Create new configuration option `enable-logind` to select which session
support functionality to build, logind or utmp. By default the option is
logind.
shadow userdel: add the adaptation to the busybox ps in 01-kill_user_procs.sh
In some embedded systems, users only use the ps
provided by the busybox. But the ps provided by
the busybox does not support the -eo option by
default. As a result, an error is reported when
the userdel is used. So add a judgment on ps.
If there is no ps -eo, traverse the process directly.
The error information is as follows:
# userdel xsl
ps: invalid option -- 'e'
Michael Vetter [Wed, 26 Jul 2023 08:13:53 +0000 (10:13 +0200)]
chsh: warn if root sets a shell not listed in /etc/shells
Print a warning even for the root user if the provided shell isn't
listed in /etc/shells, but continue to execute the action.
In case of non root user exit.
See https://github.com/shadow-maint/shadow/issues/535
Since newgrp is setuid-root, any write() system calls it does in order
to print error messages will be done as the root user.
Unprivileged users can get newgrp to print essentially arbitrary strings
to any open file in this way by passing those strings as argv[0] when
calling execve(). For example:
b1282224 (Add maximum padding to fit IPv6-Addresses, 2020-05-24) pads
the From field header using `maxIPv6Addrlen - 3`. This leaves the
Latest field header misaligned. Subtract 4 (the length of "From").
Fixes build error:
newusers.c: In function 'update_passwd':
newusers.c:433:21: error: 'sflg' undeclared (first use in this function); did you mean 'rflg'?
Jeffrey Bencteux [Wed, 21 Jun 2023 13:12:43 +0000 (15:12 +0200)]
chgpasswd: fix segfault in command-line options
Using the --sha-rounds option without first giving a crypt method via the --crypt-method option results in comparisons with a NULL pointer and thus make chgpasswd segfault:
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>
- Invert conditional to reduce indentation.
- Rewrite while loop calling strtok(3) as a for loop. This allows
doing more simplification inside the loop (see next commit).
- Fix indentation. It was very broken.
- Move variable declaration to the top of the block in which it's used.
- Reduce use of whitespace and newlines.
- Merge nested conditionals into a single if, to reduce indentation.
- Indent (1 SP) nested preprocessor conditionals.
- Reduce use of whitespace and newlines while unindenting.
If we consider simple objects as arrays of size 1, we can considerably
simplify these APIs, merging the *ARRAY and the non-array variants.
That will produce more readable code, since lines will be shorter (by
not having ARRAY in the macro names, as all macros will consistently
handle arrays), and the allocated size will be also more explicit.
The syntax will now be of the form:
p = MALLOC(42, foo_t); // allocate 42 elements of type foo_t.
p = MALLOC(1, bar_t); // allocate 1 element of type foo_t.
The _array() allocation functions should _never_ be called directly, and
instead these macros should be used.
The non-array functions (e.g., malloc(3)) still have their place, but
are limited to allocating structures with flexible array members. For
any other uses, the macros should be used.
Thus, we don't use any array or ARRAY variants in any code any more, and
they are only used as implementation details of these macros.
Those comments were written when this function used 64 bits (and
temporary variables of 128 bits). Now it uses 32 bits, with temporaries
of 64 bits, so some values have changed.
Fixes: 2a61122b5e8f ("Unoptimize the higher part of the domain of csrand_uniform()") Signed-off-by: Alejandro Colomar <alx@kernel.org>
getline(3) might have succeeded in a previous iteration, in which case
p points to an offset that is not valid. Make p NULL at the end of the
loop, to make sure it doesn't hold old stuff.
This makes the function fit in less screens. This is to avoid consuming
more natural resources than we have available, and everyone knows the
supply of new-lines on a screen is not a renewable source[1].
Some transformations have been done thanks to free(NULL) being an alias
for loopity_loop(), as defined three comits ago. The real definition of
free(3) that everyone has been hiding is this:
ch(g)passwd: Check selinux permissions upon startup
The permission also need to be checked before process_root_flag() since
that can chroot into non-selinux environment (unavailable selinux mount
point for example).
Signed-off-by: Martin Kletzander <mkletzan@redhat.com>
Some errors were being reported in stderr, but then they weren't really
being treated as errors.
If mkdir(2) for EEXIST, it's possible that the sysadmin pre-created the
user dir; don't fail. However, let's keep a log line, for having some
notice that it happened.
Also, run chmod(2) if mkdir(2) failed for EEXIST (so transform the
'else if' into an 'if').
Samanta Navarro [Tue, 23 May 2023 11:57:50 +0000 (11:57 +0000)]
libmisc: Use safer chroot/chdir sequence
OpenSSH and coreutils' chroot call chroot first and then chdir. Doing it
this way is a bit safer because otherwise something could happen between
chdir and chroot to the specified path (like exchange of links) so the
working directory would not end up within the chroot environment.
Samanta Navarro [Tue, 23 May 2023 11:55:09 +0000 (11:55 +0000)]
su: Prevent stack overflow in check_perms
This is no real world security fix.
The overflow could occur if too many layered subsystems are encountered
because the function check_perms calls itself recursively.
It would already take a misconfigured system for this to achieve it.
Use an iterative approach by calling the do_check_perms in a loop
instead of calling itself recursively.
As a side note: At least GCC 13 optimized this code and already uses
a jmp in its assembler code. I could only see the stack overflow by
activating address sanitizer which prevented the optimization.
Samanta Navarro [Tue, 23 May 2023 11:53:53 +0000 (11:53 +0000)]
subsystem: Prevent endless loop
If a user has home directory "/" and login shell "*" then login and su
enter an endless loop by constantly switching to the next subsystem.
This could also be achieved with a layered approach so just checking
for "/" as home directory is not enough to protect against such a
misconfiguration.
Just break the loop if it progressed too far. I doubt that this has
negative impact on any real setup.
Samanta Navarro [Thu, 18 May 2023 11:58:19 +0000 (11:58 +0000)]
chsh: Verify that login shell path is absolute
The getusershell implementation of musl returns every line within the
/etc/shells file, which even includes comments. Only consider absolute
paths for login shells.
Samanta Navarro [Thu, 18 May 2023 11:56:17 +0000 (11:56 +0000)]
process_prefix_flag: Drop privileges
Using --prefix in a setuid binary is quite dangerous. An unprivileged
user could prepare a custom shadow file in home directory. During a data
race the user could exchange directories with links which could lead to
exchange of shadow file in system's /etc directory.
This could be used for local privilege escalation.
Parsing optional environment variables after a login name is a feature
which is neither documented nor available in util-linux or busybox
login which are other wide spread login utilities used in Linux
distributions as reference.
Removing this feature resolves two issues:
- A memory leak exists if variables without an equal sign are used,
because set_env creates copies on its own. This could lead to OOM
situations in privileged part of login or may lead to heap spraying.
- Environment variables are not reset between login attempts. This
could lead to additional environment variables set for a user who
never intended to do so.
Proof of Concept on a system with shadow login without PAM and
util-linux agetty:
1. Provoke an invalid login, e.g. user `noone` and password `invalid`.
This starts shadow login and subsequent inputs are passed through
the function login_prompt.
2. Provoke an invalid login with environment variables, e.g.
user `noone HISTFILE=/tmp/owo` and password `invalid`.
3. Log in correctly with user `root`.
Now you can see with `echo $HISTFILE` that `/tmp/owo` has been set for
the root user.
This requires a malicious failed login attempt and a successful login
within the configured login timeout (default 60 seconds).
libmisc, man: Drop old check and advice for complex character sets in passwords
Add the relevant XKCD to the passwd(1) manual page. It already explains
most of the rationale behind this patch.
Add also reference to makepasswd(1), which is a good way to generate
strong passwords. Personally, I commonly run `makepasswd --chars 64` to
create my passwords, or 32 for passwords I need to type interactively
often.
The strength of a password is an exponential formula, where the base is
the size of the character set, and the exponent is the length of the
password. That already shows why long passwords of just lowercase
letters are better than short Pa$sw0rdZ3. But an even more important
point is that humans, when forced to use symbols in a password, are more
likely to do trivial substitutions on simple passwords, which doesn't
increase strength, and can instead give a false sense of strength, which
is dangerous.
Direct leak of 1008 byte(s) in 14 object(s) allocated from:
#0 0x5638f2e782ae in __interceptor_malloc (./src/useradd+0xee2ae)
#1 0x7fb5cfffad09 in dbase_file_init src/database_file.c:170:45
Direct leak of 392 byte(s) in 7 object(s) allocated from:
#0 0x5638f2e782ae in __interceptor_malloc (./src/useradd+0xee2ae)
#1 0x7fb5cfffc929 in dbase_policydb_init src/database_policydb.c:187:27
Direct leak of 144 byte(s) in 2 object(s) allocated from:
#0 0x5638f2e782ae in __interceptor_malloc (./src/useradd+0xee2ae)
#1 0x7fb5cfffb519 in dbase_join_init src/database_join.c:249:28
Direct leak of 40 byte(s) in 1 object(s) allocated from:
#0 0x55b230efe857 in reallocarray (./src/userdel+0xda857)
#1 0x55b230f6041f in mallocarray ./lib/./alloc.h:97:9
#2 0x55b230f6041f in commonio_open ./lib/commonio.c:563:7
#3 0x55b230f39098 in open_files ./src/userdel.c:555:6
#4 0x55b230f39098 in main ./src/userdel.c:1189:2
#5 0x7f9b48c64189 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
getline(3) is much more readable than manually looping. It has some
overhead due to the allocation of a buffer, but that shouldn't be a
problem here. If that was a problem, we could reuse the buffer (thus
making the function non-reentrant), but I don't think that's worth the
extra complexity.
Using rpmatch(3) instead of a simple y/n test provides i18n to the
response checking. We have a fall-back minimalistic implementation for
systems that lack this function (e.g., musl libc).
While we're at it, apply some other minor improvements to this file:
- Remove comment saying which files use this function. That's likely
to get outdated. And anyway, it's just a grep(1) away, so it doesn't
really add any value.
- Remove unnecessary casts to (void) that were used to verbosely ignore
errors from stdio calls. They add clutter without really adding much
value to the code (or I don't see it).
- Remove comments from the function body. They make the function less
readable. Instead, centralize the description of the function into a
man-page-like comment before the function definition. This keeps the
function body short and sweet.
- Add '#include <stdbool.h>', which was missing.
- Minor whitespace style changes (it doesn't hurt the diff at this
point, since most of the affected lines were already touched by other
changes, so I applied my preferred style :).
The tools newgrp and useradd expect waitpid to behave as described in
its manual page. But the notes indicate that if SIGCHLD is ignored,
waitpid behaves differently.
A user could set SIGCHLD to ignore before starting newgrp through exec.
Children of newgrp would not become zombies and their PIDs could be
reassigned before newgrp could call kill with the child pid and SIGCONT.
The useradd tool is not installed setuid, but I have added the default
there as well (copied from vipw).
If make fails in a multi-process invocation, the log is pretty much
unreadable. To make it readable, build as much as can be built without
failing. Then run a single-process make again. If we succeeded
previously, this should be a no-op. If not, this run will stop at the
first error, which should be more readable, and will only print the few
lines we're interested in.
This has some side effects: Now we build as much as we can, instead of
failing as early as possible; this may make CI a bit slower. However,
it also has the benefit that you see _all_ the error messages that could
be given, instead of needing to fix the first error to see the next and
so on.