143 lines
5.2 KiB
Diff
143 lines
5.2 KiB
Diff
From 65c88a43a23c2391dcc90c0abda3e839e9c57904 Mon Sep 17 00:00:00 2001
|
|
From: Alejandro Colomar <alx@kernel.org>
|
|
Date: Sat, 10 Jun 2023 16:20:05 +0200
|
|
Subject: [PATCH] gpasswd(1): Fix password leak
|
|
|
|
How to trigger this password leak?
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
When gpasswd(1) asks for the new password, it asks twice (as is usual
|
|
for confirming the new password). Each of those 2 password prompts
|
|
uses agetpass() to get the password. If the second agetpass() fails,
|
|
the first password, which has been copied into the 'static' buffer
|
|
'pass' via STRFCPY(), wasn't being zeroed.
|
|
|
|
agetpass() is defined in <./libmisc/agetpass.c> (around line 91), and
|
|
can fail for any of the following reasons:
|
|
|
|
- malloc(3) or readpassphrase(3) failure.
|
|
|
|
These are going to be difficult to trigger. Maybe getting the system
|
|
to the limits of memory utilization at that exact point, so that the
|
|
next malloc(3) gets ENOMEM, and possibly even the OOM is triggered.
|
|
About readpassphrase(3), ENFILE and EINTR seem the only plausible
|
|
ones, and EINTR probably requires privilege or being the same user;
|
|
but I wouldn't discard ENFILE so easily, if a process starts opening
|
|
files.
|
|
|
|
- The password is longer than PASS_MAX.
|
|
|
|
The is plausible with physical access. However, at that point, a
|
|
keylogger will be a much simpler attack.
|
|
|
|
And, the attacker must be able to know when the second password is being
|
|
introduced, which is not going to be easy.
|
|
|
|
How to read the password after the leak?
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Provoking the leak yourself at the right point by entering a very long
|
|
password is easy, and inspecting the process stack at that point should
|
|
be doable. Try to find some consistent patterns.
|
|
|
|
Then, search for those patterns in free memory, right after the victim
|
|
leaks their password.
|
|
|
|
Once you get the leak, a program should read all the free memory
|
|
searching for patterns that gpasswd(1) leaves nearby the leaked
|
|
password.
|
|
|
|
On 6/10/23 03:14, Seth Arnold wrote:
|
|
> An attacker process wouldn't be able to use malloc(3) for this task.
|
|
> There's a handful of tools available for userspace to allocate memory:
|
|
>
|
|
> - brk / sbrk
|
|
> - mmap MAP_ANONYMOUS
|
|
> - mmap /dev/zero
|
|
> - mmap some other file
|
|
> - shm_open
|
|
> - shmget
|
|
>
|
|
> Most of these return only pages of zeros to a process. Using mmap of an
|
|
> existing file, you can get some of the contents of the file demand-loaded
|
|
> into the memory space on the first use.
|
|
>
|
|
> The MAP_UNINITIALIZED flag only works if the kernel was compiled with
|
|
> CONFIG_MMAP_ALLOW_UNINITIALIZED. This is rare.
|
|
>
|
|
> malloc(3) doesn't zero memory, to our collective frustration, but all the
|
|
> garbage in the allocations is from previous allocations in the current
|
|
> process. It isn't leftover from other processes.
|
|
>
|
|
> The avenues available for reading the memory:
|
|
> - /dev/mem and /dev/kmem (requires root, not available with Secure Boot)
|
|
> - /proc/pid/mem (requires ptrace privileges, mediated by YAMA)
|
|
> - ptrace (requires ptrace privileges, mediated by YAMA)
|
|
> - causing memory to be swapped to disk, and then inspecting the swap
|
|
>
|
|
> These all require a certain amount of privileges.
|
|
|
|
How to fix it?
|
|
~~~~~~~~~~~~~~
|
|
|
|
memzero(), which internally calls explicit_bzero(3), or whatever
|
|
alternative the system provides with a slightly different name, will
|
|
make sure that the buffer is zeroed in memory, and optimizations are not
|
|
allowed to impede this zeroing.
|
|
|
|
This is not really 100% effective, since compilers may place copies of
|
|
the string somewhere hidden in the stack. Those copies won't get zeroed
|
|
by explicit_bzero(3). However, that's arguably a compiler bug, since
|
|
compilers should make everything possible to avoid optimizing strings
|
|
that are later passed to explicit_bzero(3). But we all know that
|
|
sometimes it's impossible to have perfect knowledge in the compiler, so
|
|
this is plausible. Nevertheless, there's nothing we can do against such
|
|
issues, except minimizing the time such passwords are stored in plain
|
|
text.
|
|
|
|
Security concerns
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
We believe this isn't easy to exploit. Nevertheless, and since the fix
|
|
is trivial, this fix should probably be applied soon, and backported to
|
|
all supported distributions, to prevent someone else having more
|
|
imagination than us to find a way.
|
|
|
|
Affected versions
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
All. Bug introduced in shadow 19990709. That's the second commit in
|
|
the git history.
|
|
|
|
Fixes: 45c6603cc86c ("[svn-upgrade] Integrating new upstream version, shadow (19990709)")
|
|
Reported-by: Alejandro Colomar <alx@kernel.org>
|
|
Cc: Serge Hallyn <serge@hallyn.com>
|
|
Cc: Iker Pedrosa <ipedrosa@redhat.com>
|
|
Cc: Seth Arnold <seth.arnold@canonical.com>
|
|
Cc: Christian Brauner <christian@brauner.io>
|
|
Cc: Balint Reczey <rbalint@debian.org>
|
|
Cc: Sam James <sam@gentoo.org>
|
|
Cc: David Runge <dvzrv@archlinux.org>
|
|
Cc: Andreas Jaeger <aj@suse.de>
|
|
Cc: <~hallyn/shadow@lists.sr.ht>
|
|
Signed-off-by: Alejandro Colomar <alx@kernel.org>
|
|
---
|
|
src/gpasswd.c | 1 +
|
|
1 file changed, 1 insertion(+)
|
|
|
|
diff --git a/src/gpasswd.c b/src/gpasswd.c
|
|
index 609fe0a4..3b76ff8e 100644
|
|
--- a/src/gpasswd.c
|
|
+++ b/src/gpasswd.c
|
|
@@ -898,6 +898,7 @@ static void change_passwd (struct group *gr)
|
|
strzero (cp);
|
|
cp = getpass (_("Re-enter new password: "));
|
|
if (NULL == cp) {
|
|
+ memzero (pass, sizeof pass);
|
|
exit (1);
|
|
}
|
|
|
|
--
|
|
2.27.0
|
|
|