strchriscntrl: reject C1 control bytes (0x80-0x9F)
glibc's iscntrl() does not classify C1 control bytes as control
characters in any locale. The iscntrl() check was added as part of the
fix for CVE-2023-29383, which blocked C0 control characters, but C1
bytes were never considered.
The byte 0x9B is the C1 encoding of CSI (Control Sequence Introducer),
equivalent to ESC [. On terminals that interpret C1 codes (VTE-based
terminals such as GNOME Terminal, Tilix, Terminator, XFCE Terminal),
an attacker can inject terminal escape sequences into GECOS fields via
chfn. This allows visual spoofing of /etc/passwd output, for example
making a user's UID appear as 0 when viewed with cat.
Explicitly check for bytes in the 0x80-0x9F range in strchriscntrl()
so that valid_field() returns -1 (rejected) instead of 1 (non-ASCII
warning) for these bytes.
Before (unpatched chfn accepts C1 bytes and writes them to /etc/passwd):
If -e 0 or -f 0 are supplied but no shadow entry exists, these flags are
silently ignored. Internally, the supplied values (0) are compared with
static long values which are initially 0 as well. If they are identical,
usermod drops the flags.
This does not match usermod's manual page, which states that a missing
shadow entry is created. This is also true for any other valid command
line values, e.g. -e 1 or -f 1.
aborah-sudo [Wed, 11 Mar 2026 08:27:42 +0000 (13:57 +0530)]
Tests: Add user with existing UID without -o flag
This is the transformation to Python of the test located in
`04_useradd_add_user_with_existing_UID_fail`, which checks that `useradd` fails to create a new user with existing UID without -o flag
aborah-sudo [Mon, 9 Mar 2026 05:50:21 +0000 (11:20 +0530)]
Tests: Add a new user with specified UID and GID
This is the transformation to Python of the test located in
`tests/usertools/01/04_useradd_specified_UID_and_GID.test`,
which checks that `useradd` is able to create a new user with specified uid and gid
lib/io/syslog.h: SYSLOG_C(): Split code to helper macro
The name of this macro makes it more evident what it does. It's a
C-locale version of syslog(3). We need to implement it as a macro
so that arguments such as strerror(3) are evaluated after setting
the C locale. This would be impossible with a function.
aborah-sudo [Mon, 2 Mar 2026 07:04:18 +0000 (12:34 +0530)]
Tests: Create user with comment, expiredate, inactive, shell and custom home
This is the transformation to Python of the test located in
`tests/usertools/01/03_useradd_additional_options.test`, which checks that
`useradd` is able to create a new user with comment, expiredate, inactive, shell and custom home
Chen Qi [Wed, 11 Feb 2026 05:17:04 +0000 (05:17 +0000)]
Fix build failure on hosts with gcc 10
We cannot just uname these unused parameters because gcc 10
need them. There will be error like below with gcc 10:
lib/copydir.c:103:11: error: parameter name omitted
The Debian bullseye uses gcc 10 by default.
Most of the changes are done manually. Some changes in tests/unit
are done via the following shell command:
sed -i -e 's#void \*\*#MAYBE_UNUSED void \*\* _1#g' tests/unit/test_*.c
Hadi Chokr [Thu, 12 Feb 2026 11:17:52 +0000 (12:17 +0100)]
tests: add privileged useradd test for BTRFS subvolume homes
Add a privileged system test that verifies useradd can create home
directories as BTRFS subvolumes.
This test requires elevated privileges to use:
The BTRFS filesystem Framework class to create a /home directory on
BTRFS and check if the Users Home directory is a BTRFS Subvolume.
The test is intentionally isolated and excluded from default test runs
to prevent accidental execution on host systems.
Hadi Chokr [Thu, 12 Feb 2026 11:16:39 +0000 (12:16 +0100)]
tests: add privileged topology and test configuration
Extend the Python system test framework with a dedicated privileged
topology and configuration, while keeping privileged tests excluded
by default. And include a BTRFS Framework class for BTRFS Operations.
This ensures:
- Clear separation between safe and destructive tests
- No accidental execution of privileged tests in normal CI or local runs
- Explicit opt-in for privileged environments
Hadi Chokr [Thu, 12 Feb 2026 11:14:54 +0000 (12:14 +0100)]
doc: document privileged containers and privileged system tests
Document the distinction between unprivileged and privileged execution
modes for containers and system tests.
Add explicit warnings that privileged tests must never be run on the
host system, as they may mount filesystems, create loop devices, or
modify kernel-visible state.
Provide clear guidance on when privileged tests are acceptable and how
to execute them safely in disposable environments.
Hadi Chokr [Thu, 12 Feb 2026 11:13:46 +0000 (12:13 +0100)]
share: add privileged container support for CI and system tests
Introduce opt-in privileged container execution for CI and local runs.
This enables filesystem-level tests (e.g. BTRFS, mounts) while keeping
unprivileged execution as the default and safe path.
Changes include:
- Separate privileged and unprivileged builders
- Conditional Ansible roles and inventories
- Privileged test execution wiring
- --privileged support in container-build.sh
A one-liner macro is simpler, and works just fine.
We don't need the function at all, since we don't use it as a callback.
The macro changes the return type, but we don't really care about that
detail; we could cast it to bool, but we don't really need that, so keep
it simple.
lib/defines.h, lib/, src/: Redefine SYSLOG() as a variadic macro
The double parentheses were used to fake a variadic macro with a
non-variadic one. With a variadic macro, we remove the weird double
parentheses that were needed to call this macro, which BTW were
error-prone. This would have prevented the bug fixed in 3f5ae5d5f1fd
(2025-09-10; "src/su.c: Fix incorrect (non-matching) parentheses").
usermod: Add option to automatically find subordinate IDs
Tools such as useradd(8) automatically select subordinate UID and GID
ranges based on settings in login.defs.
But when one wants to add subordinate IDs to an existing user, these
ranges have to be specified manually using the -w and -v options
of usermod(8).
Add a new -S / --add-subids option to usermod(8) which will, just like
useradd(8), find a range based on the settings in login.defs.
Signed-off-by: Richard Weinberger <richard@nod.at>
lib/string/README: streq(3) and strnul(3) are in libc
streq(3) and strnul(3) are now part of a libc implementation: gnulib.
They are also documented in a manual page. Thus, refer to them as
streq(3) and strnul(3).
It has been deprecated for a very long time. In fact, the first commit
that documented MD5_CRYPT_ENAB already documented it as deprecated. 6e3ad7a27546 (2007-11-20).
Password expiration is deprecated, and will be eventually removed.
The functionality of expiry(1) is the most superfluous of password
expiry and can be removed early. This shouldn't conflict with any
existing regulations about password expiry.
Iker Pedrosa [Fri, 6 Feb 2026 15:31:56 +0000 (16:31 +0100)]
lib/chkhash.c: fix yescrypt hash length comment
Fix misleading comment that stated "43-char (minimum) hash" when
the actual regex pattern requires exactly 43 characters. Update
comment to accurately reflect the implementation behavior.
And update lib/string/README:
- Rename MEMDUP() => memdup_T(), as we're moving away from upper-case
macros to ones that actually say something about what they do in the
name (_T for type-safe, and _a for array-safe).
- memdup() is unimplemented.
lib/utmp.c: get_current_utmp(): Use simple assignment instead of memcpy(3)
memcpy(3) is overkill, and much more dangerous than simple assignment.
Simple assignment adds type safety, and removes any possibility of
buffer overflow due to accidentally specifying a wrong size.
Mike Gilbert [Mon, 16 Feb 2026 20:36:37 +0000 (15:36 -0500)]
configure.ac: fix detection of secure_getenv
lib/defines.h was looking for HAVE_SECURE_GETENV instead of of
HAS_SECURE_GETENV as defined in configure. This resulted in
shadow_getenv always being defined to getenv.
AC_CHECK_FUNC is linker test; it does not check for declarations.
Replace this with AC_CHECK_DECLS/HAVE_DECL_SECURE_GETENV.
Fixes: 3d921155e0a7 (2019-03-31; "gettime: Use secure_getenv over getenv.") Signed-off-by: Mike Gilbert <floppym@gentoo.org>
Iker Pedrosa [Fri, 13 Feb 2026 08:08:46 +0000 (09:08 +0100)]
src/passwd.c: add audit messages for passwd
Add comprehensive audit messages for password operations, including
unlock, delete and expire operations.
Change update_shadow() to look up the actual target user instead of
using a dummy `passwd` struct. This ensures audit logging gets the
correct target UID. Audit logs should record the UID of the user being
affected (target), not the UID of whoever is running the passwd command
(source).
Adam Williamson [Tue, 27 Jan 2026 23:11:07 +0000 (15:11 -0800)]
lib/chkhash.c: fix escaping in SHA-256 / SHA-512 / MD5 regexes
`\\n` inside square brackets doesn't include or exclude the
newline character. It includes or excludes a literal slash and
the literal character 'n'.
Fixes: c44f1e096a19 (2025-07-20; "chpasswd: Check hash before write when using -e") Closes: <https://github.com/shadow-maint/shadow/issues/1519> Signed-off-by: Adam Williamson <awilliam@redhat.com>
lib/chkhash.c: is_valid_hash(): Accept an empty hash
It represents a passwordless account.
That is discouraged, but accepted.
Fixes: c44f1e096a19 (2025-07-20; "chpasswd: Check hash before write when using -e")
Link: <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1124835> Reported-by: Marc 'Zugschlus' Haber <mh+githubvisible@zugschlus.de> Reported-by: "Serge E. Hallyn" <serge@hallyn.com> Reported-by: Adam Williamson <awilliam@redhat.com> Co-authored-by: "Serge E. Hallyn" <serge@hallyn.com> Signed-off-by: Alejandro Colomar <alx@kernel.org>
This wasn't only an optimization; it also skipped some checks that were
now spuriously triggering errors. We may be able to get rid of the
optimizations, but that will need more analysis. For now, let's revert
to a known-good state.
src/usermod.c: -U: Report E_PASSWORDLESS on error due to passwordless account
Reproducer:
$ useradd foo
$ grep foo /etc/passwd /etc/shadow
/etc/passwd:foo:x:1001:1001::/home/foo:/usr/bin/bash
/etc/shadow:foo:!:20458:0:99999:7:::
$ usermod -U testuser
usermod: unlocking the user's password would result in a passwordless account.
You should set a password with usermod -p to unlock this user's password.
$ echo $?
0
$ grep foo /etc/passwd /etc/shadow
/etc/passwd:foo:x:1001:1001::/home/foo:/usr/bin/bash
/etc/shadow:foo:!:20458:0:99999:7:::
The program failed (didn't change anything, and reported the problem to
stderr) but reported success (0). After this patch, the error is
reported as E_PASSWORDLESS (20).
The lrename function follows symlinks when renaming files. Since the
source is a temporary file and the target is the database file itself,
which is opened with O_NOFOLLOW, this function is only useful for an
attacker who manages to win some form of race.
The fmkomstemp call requires a suffix of XXXXXX for correct operation.
Do so in TCB case as well.
Note: If something fails and the file resides in this directory, it
could be interpreted as a username. Use the ',' character as an illegal
character to prevent shadow tools from erroneously accessing this file
and assuming that the user actually exists.
Fixes: a5b3d56e2902 (2026-01-09; "vipw: Use fmkomstemp for temporary file") Reported-by: Alejandro Colomar <alx@kernel.org> Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
Use file descriptor functions when file descriptor is available, instead
of path based operations. The latter resolve symbolic links and are
prone to race conditions.
Make sure that an attacker with sufficient privileges cannot simply
create a file with expected temporary name to retrieve content of
previous and/or future database.
Accessing this variable directly is a recipe for disaster, because
binaries and libraries can have different versions in them due to how
libshadow_la linking is performed.
Make sure that at least NULL check is always performed by calling the
proper getter function.
Do not call any shadowlog functions directly from program source files
which are also linked with libsubid.
Both, the program and the library, will have their own version of the
static variables within shadowlog.c and thus would have different
logging mechanisms.