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.
It could happen that, if SIGCHLD was set to SIG_IGN before calling vipw,
the forked child is already gone before SIGCHLD is set to SIG_DFL after
the fork.
Prevent this race condition and also properly set up SIGCHLD for child
handling within the fork, even though system() should take care of that.
If TCB is not in use, the whole configuration section is a stub,
containing no useful information. Make it conditional so it
disappears if TCB is not in use.
Iker Pedrosa [Tue, 23 Dec 2025 08:51:56 +0000 (09:51 +0100)]
tests/system/tests/test_groupmod.py: add test for groupmod -U with user list
Add comprehensive test for the groupmod -U option when provided with a
list of users to set group membership. This test verifies:
- Setting initial group membership with multiple users
- Proper membership verification in both group and gshadow entries
- Updating group membership by modifying the user list
- Correct handling of membership changes in group databases
GShadowEntry administrators and members represent a list of usernames,
not a single string. Thus, set them to `list[str]`. This fixes type
safety and clarifies the expected data structure.
- Remove support for escaped newlines in configuration files.
It never worked correctly. b0a7ce58b924 (2025-12-05; "lib/, po/: Remove fgetsx() and fputsx()")
- Some user names and group names are too dangerous and are rejected,
even with --badname. 25aea7422615 (2025-12-25; "lib/chkname.c, src/: Strictly disallow really bad names")
Future breaking changes:
- SHA512 and SHA256 will be supported unconditionally in the next
release. The build-time flag '--with-sha-crypt' will be removed.
See <https://github.com/shadow-maint/shadow/pull/1452>.
Support:
- Several years ago, there were talks about deprecating su(1) and
login(1), back when this project was maintained as part of Debian.
However, nothing was clearly stated, and there were doubts about the
status of these programs. Let's clarify them now.
Our implementations of su(1) and login(1) are fully supported, and we
don't have any plans to remove them. They are NOT deprecated.
See <https://github.com/shadow-maint/shadow/issues/464>.
Deprecations:
- groupmems(8)
The program will be removed in a future release.
See <https://github.com/shadow-maint/shadow/issues/1343>.
- logoutd(8)
The program will be removed in the next release.
See <https://github.com/shadow-maint/shadow/issues/999>,
and <https://github.com/shadow-maint/shadow/pull/1344>.
- DES
This hashing algorithm has been deprecated for a long time,
and support for it will be removed in a future release.
See <https://github.com/shadow-maint/shadow/pull/1456>
- MD5
This hashing algorithm has been deprecated for a long time,
and support for it will be removed in a future release.
See <https://github.com/shadow-maint/shadow/pull/1457>
- login.defs(5): MD_CRYPT_ENAB
This feature had been deprecated for decades. It will be
removed in a future release.
The command-line equivalents (-m, --md5) of this feature in
chpasswd(8) and chgpasswd(8) will also be removed in a future
release.
See <https://github.com/shadow-maint/shadow/pull/1455>.
- login.defs(5): PASS_MAX_LEN
This feature is ignored except for DES. Once DES is removed,
it makes no sense keeping it. It may be removed in a future
release.
- Password aging
Scientific research shows that periodic password expiration
leads to predictable password patterns, and that even in a
theoretical scenario where that wouldn't happen the gains in
security are mathematically negligible.
<https://people.scs.carleton.ca/~paulv/papers/expiration-authorcopy.pdf>
Modern security standards, such as NIST SP 800-63B-4 in the USA,
prohibit periodic password expiration.
<https://pages.nist.gov/800-63-4/sp800-63b.html#passwordver>
<https://pages.nist.gov/800-63-FAQ/#q-b05>
<https://www.ncsc.gov.uk/collection/passwords/updating-your-approach#PasswordGuidance:UpdatingYourApproach-Don'tenforceregularpasswordexpiry>
To align with these, we're deprecating the ability to
periodically expire passwords. The specifics and long-term
roadmap are currently being discussed, and we invite feedback
from users, particularly from those in regulated environments.
See <https://github.com/shadow-maint/shadow/pull/1432>.
This deprecation includes the following programs and features:
expiry(1)
chage(1):
-I,--inactive (also the interactive version)
-m,--mindays (also the interactive version)
-M,--maxdays (also the interactive version)
-W,--warndays (also the interactive version)
passwd(1):
-k,--keep-tokens
-n,--mindays
-x,--maxdays
-i,--inactive
-w,--warndays
useradd(8):
-f,--inactive
usermod(8):
-f,--inactive
login.defs(5):
PASS_MIN_DAYS
PASS_MAX_DAYS
PASS_WARN_AGE
/etc/default/useradd:
INACTIVE
shadow(5):
sp_lstchg: Restrict to just the values 0 and empty.
sp_min
sp_max
sp_warn
sp_inact
We recognize that many users operate in environments with
regulatory or contractual requirements that still mandate
password aging. To minimize disruption, these features will
remain functional for a significant period. However, we
encourage administrators to review their internal policies,
talk to their regulators if appropriate, and participate in the
roadmap discussion linked above.
I don't know what this commit does, to be honest. I just
did './autogen.sh && make && make dist' and committed the
changes to .po files. Why? I don't know.
BTW, I kept out some changes that were actually bad.
I don't know what this commit does, to be honest. I just
did './autogen.sh && make && make dist' and committed the
changes to .pot files. Why? I don't know.
lib/, src/: Some empty lists have 0 elements, not 1 empty string
In general, empty fields in a CSV are errors. However, in some cases,
we want to allow passing empty lists, and the way to encode that is as
an empty string. This was accidentally broken in 4.17.0, when we
switched from using strtok(3) to strsep(3), without remembering to
special-case an empty CSV.
The bug affected directly groupadd(8) and groupmod(8).
The bug also affected the library function add_groups(). In systems
using PAM, that function is unused. On systems without PAM, it is
called by the library function setup_uid_gid(), with the contents of the
"CONSOLE_GROUPS" configuration (login.defs) CSV string.
setup_uid_gid() is directly called by su(1) and login(1) on systems
without PAM.
setup_uid_gid() is also called by the library function expire().
expire() is directly called by expiry(1), su(1), and login(1).
This bug is a regression introduced in the release 4.17.0, and present
in the releases 4.17.{0..4} and 4.18.0.
Fixes: 90afe61003ef (2024-12-05; "lib/, src/: Use strsep(3) instead of strtok(3)")
Link: <https://github.com/shadow-maint/shadow/issues/1420> Reported-by: Osark Vieira <https://github.com/osark084> Signed-off-by: Alejandro Colomar <alx@kernel.org>
lib/chkname.c, src/: Strictly disallow really bad names
Some names are bad, and some names are really bad. '--badname' should
only allow the mildly bad ones, which we can handle. Some names are too
bad, and it's not possible to deal with them. Reject them
unconditionally.
- A leading '-' is too dangerous. It breaks things like execve(2), and
almost every command.
- Spaces are used for delimiting lists of users and groups.
- '"' is special in many languages, including the shell. Having it in
user names would be unnecessarily dangerous.
- '#' is used for delimiting comments in several of our config files.
Having it in usernames could result in incorrect configuration files.
- "'" is special in many languages, including the shell. Having it in
user names would be unnecessarily dangerous.
- ',' is used for delimiting lists of users and groups.
- '/' is used for delimiting files, and thus could result in incorrect
handling of users and groups.
- ':' is the main delimiter in /etc/shadow and /etc/passwd.
- ';' is special in many languages, including the shell. Having it in
user names would be unnecessarily dangerous.
There are other characters that we should disallow, but they need more
research to make sure we don't introduce regressions. This set should
be less problematic.
Acked-by: Tobias Stoeckmann <tobias@stoeckmann.org> Reviewed-by: Iker Pedrosa <ipedrosa@redhat.com> Reviewed-by: Chris Hofstaedtler <zeha@debian.org> Cc: Marc 'Zugschlus' Haber <mh+githubvisible@zugschlus.de> Cc: Serge Hallyn <serge@hallyn.com> Signed-off-by: Alejandro Colomar <alx@kernel.org>
Duplicating name and hash is not needed here, because duplication
occurs in spw_update. You can detect the small memory leak with
tools like valgrind.
More importantly though, if xstrdup fails, it calls exit. The
update_age function is in the "criticial section" between
open_files and close_files, though. Correct error handling would
require fail_exit to release the held locks.
Iker Pedrosa [Fri, 19 Dec 2025 15:27:54 +0000 (16:27 +0100)]
src/gpasswd.c: fix segfault in clean up callbacks
The gpasswd utility was segfaulting when cleanup functions were called
because these functions expect a pointer to `process_selinux` but was
being passed NULL. This caused a NULL pointer dereference.
This commits adds the pointer to `process_selinux` to clean up
functions making `gpasswd` consistent with other group utilities.
Reproduction steps:
$ useradd tuser
$ groupadd tuser
$ gpasswd -a tuser tgroup
Adding user tuser to group tgroup
Segmentation fault (core dumped)
Unify the retrieval of PASS_MIN_LEN and PASS_MAX_LEN for output
in passwd and actual checks.
Fixes wrong output for minimum password lengths if no such
restriction is configured: 5 is printed, 0 is in effect.
How to reproduce:
1. Use passwd compiled without PAM support
2. Do not specify PASS_MIN_LEN in login.defs
3. Run passwd as a user and enter your old password, then
- you will see that 5 characters are expected
- you can just press enter twice
The getdef_num implementation allows -1 to be specified in login.defs.
In general, -1 should be treated the same way as "not specified". In
this case, casting -1 to size_t leads to every password being "too
short."
The check_fds function is supposed to ensure that fds 0, 1, and 2 are
opened in a well-defined state, i.e. either they are already connected
to supposed input/output files or will be connected to /dev/null if not.
Opening the audit socket before checking the fds allows the audit socket
to get one of these numbers.
Avoid this by opening the audit socket after the check.
In general, this check is already covered by system libraries, but this
proof of concept works for root user. Note the different states of the
file descriptor 2.
In bash or another shell that interprets `2>&-` as closing stderr with
shadow + audit support, e.g. Arch Linux:
```
sg bin 'ls -l /proc/self/fd'
sg bin 'ls -l /proc/self/fd' 2>/dev/null
sg bin 'ls -l /proc/self/fd' 2>&-
```
The `PASS_MAX_LEN` is effectively only used for DES. Do not describe it
in a way that makes it sound like `MD_CRYPT_ENAB=yes` is required to
disable it. Any other `ENCRYPT_METHOD` disables it as well.
Also, even for DES, `PASS_MAX_LEN` requires `OBSCURE_CHECKS_ENAB` to
have any effect.
Even more, `PASS_MIN_LEN` and `PASS_MAX_LEN` are only used for
user passwords. Group passwords are not checked.
Note: All of this is actually true even if compiled with PAM if command
line arguments change root. But if compiled with PAM support, this
section is not added to manual pages... Since this is true for some
more files, it's not part of this commit.
Link to source files:
- lib/obscure.c line 133 stops further checks, including max length,
if OBSCURE_CHECS_ENAB is not yes
- lib/obscure.c line 172 is only reached in case of DES
- src/passwd.c line 248 duplicates the check for output
- src/gpasswd.c has no reference to obscure
- The total number of password change tries can be configured
- Except min length, password strength checks can be disabled
- Even the root user can have password strength checks...
- ... except in some cases (stdin, command line arguments)
In general, this code does not run for PAM, except root directory
is modified through command line arguments by root user.
The passwd tool checks if the password of a user may be changed before
locking the passwd/shadow files. This leaves a time window to perform
the same action twice (e.g. circumventing PASS_MIN_DAYS limit) or to
circumvent a locked password by an administrator.
Perform the check after the lock again. This keeps the behavior as it
is today for a user and also prevents the race condition.
Always use the name in shadow entry for logging. This reduces the
amount of data retrieved from password entry to bare minimum, i.e.
passing through into library call.