View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0011782 | Rocky-Linux-10 | liboauth | public | 2026-01-20 10:37 | 2026-01-20 10:41 |
| Reporter | Benjamin Mare | Assigned To | |||
| Priority | normal | Severity | major | Reproducibility | always |
| Status | new | Resolution | open | ||
| Platform | Virtual Machine | OS | Rocky Linux | OS Version | 10.1 |
| Product Version | 10.1 | ||||
| Summary | 0011782: liboath 2.6.12-1 doesn't work if usersfile contains $HOME or $USER | ||||
| Description | Dear Maintainer, Due to CVE-2024-47191 liboath now drops privileges if the OTP token (the "usersfile" given in the pam configuration) is set to a path containing "$HOME" or "$USER". The default lockfile location is "/var/lock/pam_oath.lock", which is writable only by root. On an OTP check, the process is to drop privileges, then ask for OTP, and if the OTP is okay to modify the "usersfile" (by creatting first an exclusive lock). If the lock can"t be created, OTP fails. I can specify a lockfile manually, but I can't use "$HOME" or "$USER" so each user's lockfile is independent from one user to another. | ||||
| Steps To Reproduce | Generate a TOTP secret and store it in the user's home at a given path ``` $ id my_user_id $ cat <<EOF >~/.users_oath HOTP/T30 my_user_id - d0f1eb22502564af21e5fbb4a2b8095f862d864c EOF $ sudo cat /etc/pam.d/su #%PAM-1.0 auth required pam_env.so auth sufficient pam_rootok.so # Uncomment the following line to implicitly trust users in the "wheel" group. #auth sufficient pam_wheel.so trust use_uid # Uncomment the following line to require a user to be in the "wheel" group. #auth required pam_wheel.so use_uid auth required pam_oath.so debug usersfile=${HOME}/.users_oath windows=20 auth substack system-auth auth include postlogin account sufficient pam_succeed_if.so uid = 0 use_uid quiet account include system-auth password include system-auth session include system-auth session include postlogin session optional pam_xauth.so ``` > Note the use of "debug" flag on the "pam_oath.so" line and the "${HOME}". > Note lockfile is not sepcified< > The secret given here "d0f1eb22502564af21e5fbb4a2b8095f862d864c" is just a test example and I know it shouldn't be used on a production service because it is now published, don't worry. If I try to login with a valid TOTP (I've check it was working with oath-tool before), It will state "System error when locking file": ``` $ su my_user_id [pam_oath.c:parse_cfg(127)] called. [pam_oath.c:parse_cfg(128)] flags 0 argc 3 [pam_oath.c:parse_cfg(130)] argv[0]=debug [pam_oath.c:parse_cfg(130)] argv[1]=usersfile=${HOME}/.users_oath [pam_oath.c:parse_cfg(130)] argv[2]=windows=20 [pam_oath.c:parse_cfg(131)] debug=1 [pam_oath.c:parse_cfg(132)] alwaysok=0 [pam_oath.c:parse_cfg(133)] try_first_pass=0 [pam_oath.c:parse_cfg(134)] use_first_pass=0 [pam_oath.c:parse_cfg(135)] usersfile=${HOME}/.users_oath [pam_oath.c:parse_cfg(136)] lockfile=(null) [pam_oath.c:parse_cfg(137)] digits=0 [pam_oath.c:parse_cfg(138)] window=5 [pam_oath.c:pam_sm_authenticate(301)] get user returned: my_user_id [pam_oath.c:pam_sm_authenticate(311)] usersfile is /home/my_user_id/.users_oath (id 1001/1001) [pam_oath.c:pam_sm_authenticate(329)] Successfully dropped effective id to 1001/1001 [pam_oath.c:pam_sm_authenticate(340)] authenticate first pass rc -2 (OATH_INVALID_DIGITS: Unsupported number of OTP digits) last otp Tue Jan 20 10:10:09 2026 One-time password (OATH) for `my_user_id': [pam_oath.c:pam_sm_authenticate(434)] conv returned: 017511 [pam_oath.c:pam_sm_authenticate(498)] OTP: 017511 [pam_oath.c:pam_sm_authenticate(506)] authenticate rc -15 (OATH_FILE_LOCK_ERROR: System error when locking file) last otp Tue Jan 20 10:10:09 2026 [pam_oath.c:pam_sm_authenticate(513)] One-time password not authorized to login as user 'my_user_id' [pam_oath.c:pam_sm_authenticate(534)] Successfully restored effective id to 0/1001 [pam_oath.c:pam_sm_authenticate(546)] done. [Authentication failure] ``` With a "strace" I can see the path of the lockfile used. I won't post here the total strace but only the part that seems interesting to me: ``` $ strace su my_user_id [...] write(2, "One-time password (OATH) for `bm"..., 38One-time password (OATH) for `my_user_id': ) = 38 read(0, "114848\n", 4095) = 7 ioctl(0, TCSETSW, {c_iflag=ICRNL|IXON|IXANY|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|IEXTEN, ...}) = 0 write(2, "\n", 1 ) = 1 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 ioctl(0, TCSETSW, {c_iflag=ICRNL|IXON|IXANY|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|IEXTEN, ...}) = 0 write(1, "[pam_oath.c:pam_sm_authenticate("..., 60[pam_oath.c:pam_sm_authenticate(434)] conv returned: 114848 ) = 60 write(1, "[pam_oath.c:pam_sm_authenticate("..., 50[pam_oath.c:pam_sm_authenticate(498)] OTP: 114848 ) = 50 openat(AT_FDCWD, "/home/my_user_id/.users_oath", O_RDONLY) = 4 fstat(4, {st_mode=S_IFREG|0600, st_size=88, ...}) = 0 read(4, "HOTP/T30\tmy_user_id\t-\td0f1eb22502564a"..., 4096) = 88 newfstatat(AT_FDCWD, "/etc/localtime", {st_mode=S_IFREG|0644, st_size=2962, ...}, 0) = 0 umask(0177177) = 022 lseek(4, 0, SEEK_SET) = 0 openat(AT_FDCWD, "/var/lock/pam_oath.lock", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0666) = -1 EACCES (Permission denied) umask(022) = 0177 lseek(4, 0, SEEK_CUR) = 0 close(4) = 0 newfstatat(AT_FDCWD, "/etc/localtime", {st_mode=S_IFREG|0644, st_size=2962, ...}, 0) = 0 write(1, "[pam_oath.c:pam_sm_authenticate("..., 147[pam_oath.c:pam_sm_authenticate(506)] authenticate rc -15 (OATH_FILE_LOCK_ERROR: System error when locking file) last otp Tue Jan 20 10:10:09 2026 ) = 147 write(1, "\n", 1 ) = 1 write(1, "[pam_oath.c:pam_sm_authenticate("..., 96[pam_oath.c:pam_sm_authenticate(513)] One-time password not authorized to login as user 'my_user_id' ) = 96 setresgid(-1, 1001, -1) = 0 setresuid(-1, 1001, -1) = 0 write(1, "[pam_oath.c:pam_sm_authenticate("..., 85[pam_oath.c:pam_sm_authenticate(534)] Successfully restored effective id to 1001/1001 ) = 85 openat(AT_FDCWD, "/usr/share/locale/C.UTF-8/LC_MESSAGES/Linux-PAM.mo", O_RDONLY) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/share/locale/C.utf8/LC_MESSAGES/Linux-PAM.mo", O_RDONLY) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/share/locale/C/LC_MESSAGES/Linux-PAM.mo", O_RDONLY) = -1 ENOENT (No such file or directory) write(1, "[pam_oath.c:pam_sm_authenticate("..., 69[pam_oath.c:pam_sm_authenticate(546)] done. [Authentication failure] ) = 69 [...] ``` Here I can see the reel error with the real filename: openat(AT_FDCWD, "/var/lock/pam_oath.lock", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0666) = -1 EACCES (Permission denied) If I use a path with the "${HOME}" or ${USER}" it expand this variables in the lockfile context: ``` $ sudo cat /etc/pam.d/su #%PAM-1.0 auth required pam_env.so auth sufficient pam_rootok.so # Uncomment the following line to implicitly trust users in the "wheel" group. #auth sufficient pam_wheel.so trust use_uid # Uncomment the following line to require a user to be in the "wheel" group. #auth required pam_wheel.so use_uid auth required pam_oath.so debug usersfile=${HOME}/.users_oath windows=20 lockfile=${HOME}/.users_oath.lock auth substack system-auth auth include postlogin account sufficient pam_succeed_if.so uid = 0 use_uid quiet account include system-auth password include system-auth session include system-auth session include postlogin session optional pam_xauth.so $ strace su my_user_id [...] write(2, "One-time password (OATH) for `bm"..., 38One-time password (OATH) for `my_user_id': ) = 38 read(0, "322233\n", 4095) = 7 ioctl(0, TCSETSW, {c_iflag=ICRNL|IXON|IXANY|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|IEXTEN, ...}) = 0 write(2, "\n", 1 ) = 1 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 ioctl(0, TCSETSW, {c_iflag=ICRNL|IXON|IXANY|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|IEXTEN, ...}) = 0 write(1, "[pam_oath.c:pam_sm_authenticate("..., 60[pam_oath.c:pam_sm_authenticate(434)] conv returned: 322233 ) = 60 write(1, "[pam_oath.c:pam_sm_authenticate("..., 50[pam_oath.c:pam_sm_authenticate(498)] OTP: 322233 ) = 50 openat(AT_FDCWD, "/home/my_user_id/.users_oath", O_RDONLY) = 5 fstat(5, {st_mode=S_IFREG|0600, st_size=88, ...}) = 0 read(5, "HOTP/T30\tmy_user_id\t-\td0f1eb22502564a"..., 4096) = 88 newfstatat(AT_FDCWD, "/etc/localtime", {st_mode=S_IFREG|0644, st_size=2962, ...}, 0) = 0 umask(0177177) = 022 lseek(5, 0, SEEK_SET) = 0 openat(AT_FDCWD, "${HOME}/.users_oath.lock", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0666) = -1 ENOENT (No such file or directory) umask(022) = 0177 lseek(5, 0, SEEK_CUR) = 0 close(5) = 0 newfstatat(AT_FDCWD, "/etc/localtime", {st_mode=S_IFREG|0644, st_size=2962, ...}, 0) = 0 write(1, "[pam_oath.c:pam_sm_authenticate("..., 147[pam_oath.c:pam_sm_authenticate(506)] authenticate rc -15 (OATH_FILE_LOCK_ERROR: System error when locking file) last otp Tue Jan 20 10:10:09 2026 ) = 147 write(1, "\n", 1 ) = 1 write(1, "[pam_oath.c:pam_sm_authenticate("..., 96[pam_oath.c:pam_sm_authenticate(513)] One-time password not authorized to login as user 'my_user_id' ) = 96 setresgid(-1, 1001, -1) = 0 setresuid(-1, 1001, -1) = 0 write(1, "[pam_oath.c:pam_sm_authenticate("..., 85[pam_oath.c:pam_sm_authenticate(534)] Successfully restored effective id to 1001/1001 ) = 85 openat(AT_FDCWD, "/usr/share/locale/C.UTF-8/LC_MESSAGES/Linux-PAM.mo", O_RDONLY) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/share/locale/C.utf8/LC_MESSAGES/Linux-PAM.mo", O_RDONLY) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/share/locale/C/LC_MESSAGES/Linux-PAM.mo", O_RDONLY) = -1 ENOENT (No such file or directory) write(1, "[pam_oath.c:pam_sm_authenticate("..., 69[pam_oath.c:pam_sm_authenticate(546)] done. [Authentication failure] ) = 69 [...] ``` > We can see that $HOME is expanded for the usersfile with the line `openat(AT_FDCWD, "/home/my_user_id/.users_oath", O_RDONLY) = 5` > But the lock is not `openat(AT_FDCWD, "${HOME}/.users_oath.lock", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0666) = -1 ENOENT (No such file or directory)` If I set the lockfile to something like "/tmp/my_lock" everything will works because /tmp/ is a valid path and everybody can write to it. But the issue is that a single user could block every OTP connections by just creating a file "/tmp/my_lock" which will blocks every locks attempts (kind of service denials). | ||||
| Additional Information | SELinux is disabled for these tests to make sure it has nothing to do with the issue. Locks are working on my home, for example: ``` $( flock -n 9 || echo "lock failed" echo "lock working" ) 9>/home/my_user_id/my_lock lock working ``` If I try to cheat and set the lockfile to something like `/proc/self/cwd/my_lock` I'm able to create a lock in the users home (if the CWD is the users home). To summarize, the issue is linked to 2 things: - Permissions to read/write to the lockfile when dropping privileges - Choosing the right path for the lockfile so each user has a unique lock to avoid denials of service I think liboath should automatically change the lockfile to the user's home when "$HOME" or "$USER" is detected for usersfile, and I think the "lockfile" parameter to pam_oath.so should expand $HOME and $USER. | ||||
| Tags | No tags attached. | ||||
|
I'm sorry but, in fact, I've done this in Rocky 9.5 and not Rocky 10.1. But the package version is the same between Rocky 9 and Rocky 10 (liboath 2.6.12-1). |
|
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-01-20 10:37 | Benjamin Mare | New Issue | |
| 2026-01-20 10:41 | Benjamin Mare | Note Added: 0012475 |