- API
- Projects depending on utmp/wtmp/btmp/lastlog
- Usage Summary
- Upstream status and solutions
- Ideas for solutions
- Action Plan
- Upstream Issues and PRs
- Blogs
For background on the Y2038 problem (32bit time_t counter will overflow) I suggest to start with the wikipedia Year 2038 problem article.
The general statement so far has always been that on 64bit systems with a 64bit time_t you are safe with respect to the Y2038 problem.
But this doesn't seem to be correct: on bi-arch systems like x86-64 (so which can execute 64bit and 32bit binaries) glibc defines __WORDSIZE_TIME64_COMPAT32
, which leads to the fact, that struct lastlog
and struct utmp
uses int32_t instead of time_t. So we have a Y2038 problem, which is not easy fixable, as this would require ABI and on disk format changes.
Affected is everything, which includes utmp.h
, utmpx.h
, lastlog.h
accesses /run/utmp, /var/log/wtmp, /var/log/btmp or uses the corresponding functions from glibc.
- utmp.h, bits/utmp.h
- utmpx.h, bits/utmpx.h
- lastlog.h
From glibc:
- getutent() (ABI breakage)
- getutent_r() (ABI breakage)
- getutxent() (ABI breakage)
- getutid() (ABI breakage)
- getutid_r() (ABI breakage)
- getutxid() (ABI breakage)
- getutline() (ABI breakage)
- getutline_r() (ABI breakage)
- getutxline() (ABI breakage)
- getutmp() (ABI breakage)
- getutmpx() (ABI breakage)
- login() (ABI breakage)
- pututline() (ABI breakage)
- pututxline() (ABI breakage)
- updwtmp() (ABI breakage, WTMP file name)
- updwtmpx() (ABI breakage, WTMPX file name)
- utmpname() (UTMP file name/_PATH_UTMP)
- utmpxname() (UTMPX file name/_PATH_UTMPX)
Safe functions partly using utmp/wtmp internal:
Part of utmp.h but not using utmp/wtmp/lastlog:
Paths which may need to be changed, to have the old utmp/wtmp/btmp/lastlog and new in parallel (all defined in paths.h
:
- UTMPX_FILE
/var/run/utmp
- UTMPX_FILENAME
/var/run/utmp
- WTMPX_FILE
/var/log/wtmp
- WTMPX_FILENAME
/var/log/wtmp
- _PATH_LASTLOG
/var/log/lastlog
- _PATH_UTMP
/var/run/utmp
- _PATH_UTMPX
/var/run/utmp
- _PATH_WTMP
/var/log/wtmp
- _PATH_WTMPX
/var/log/wtmp
Several locations:
- _PATH_BTMP
/var/log/btmp
The following projects are affected. The data is collected from a server installation of openSUSE Tumbleweed. This means, I did only analyze tools from a package which are used on openSUSE, there is much more code using utmp. The ones with a ✔️ contain either alreay code to use logind or patches exist. The ones with ✅ can be ignored, the ones with a ❌ needs to be analysed and adjusted.
- ❌ accountsservice - needs to be ported to wtmpdb
- accounts-daemon seems to read logout times from /var/log/wtmp
- utmpxname()
- setutxent()/getutxent()/endutxent()
- /var/log/wtmp
- accounts-daemon seems to read logout times from /var/log/wtmp
- ❌ acct, seems to be dead.
- ac uses the wtmp to report connect time
- /var/log/wtmp
- dump-utmp print an utmp or wtmp file in human-readable format
- /var/log/wtmp
- ac uses the wtmp to report connect time
- ❌ adjtimex uses wtmp to find out BOOT_TIME and NEW_TIME
- utmpname()
- setutent()/getutent()/endutent()
- /var/log/wtmp
- ❌ busybox reads utmp for ttys, number of users, who, accesses wtmp directly
- setutxent()/getutxent()/endutxent()
- pututxline()
- updwtmpx()
- /run/utmp
- /var/log/wtmp
- ✔️ coreutils >= 9.4
- pinky reads /run/utmp and prints entries
- utmpxname()
- setutxent()/getutxent()/endutxent()
- /run/utmp
- uptime reads /run/utmp or user specified file, counts active users and determines boot time if there is no /proc/uptime.
- utmpxname()
- setutxent()/getutxent()/endutxent()
- /run/utmp
- /var/log/wtmp
- users prints login names from /run/utmp
- utmpxname()
- setutxent()/getutxent()/endutxent()
- /run/utmp
- /var/log/wtmp
- who prints data out of utmp about active users
- utmpxname()
- setutxent()/getutxent()/endutxent()
- /run/utmp
- pinky reads /run/utmp and prints entries
- ✔️ emacs >= git from 2023-08-15 (tries to get the boot time from /run/utmp)
- utmpname()
- setutent()/getutent()/endutent()
- getutid()
- /var/log/wtmp
- ✅ gdm writes wtmp and utmp entry - can be replaced with PAM modules for logind, lastlog2 and wtmpdb.
- setutxent()
- pututxline()
- endutxent()
- updwtmpx()
- /var/log/wtmp
- /var/log/btmp
- ❌ gpm
- gpm-root marks entry as "DEAD_PROCESS"
- setutent()
- getutline()
- pututline()
- gpm-root marks entry as "DEAD_PROCESS"
- ✔️ monitoring-plugins > 2.3.3 (PR:check_users: prefer systemd-logind over utmp)
- check_users counts number of logged in users
- setutxent()/getuxent()/endutxent()
- check_users counts number of logged in users
- ❌ net-snmp
- libnetsnmpmibs.40.2.0 counts number of logged in users, can be replaced with
sd_get_sessions(NULL)
- setutxent()/getutxent()/endutxent()
- libnetsnmpmibs.40.2.0 counts number of logged in users, can be replaced with
- ✔️ openssh - PR:Add wtmpdb support as Y2038 safe wtmp replacement, logind-set-tty.patch
- sshd let glibc write the utmp/wtmp entries, but does not use them itself.
- login()
- logout()
- logwtmp()
- /var/log/btmp (always enabled, not configureable)
- /var/log/lastlog (writing by default enabled)
- sshd let glibc write the utmp/wtmp entries, but does not use them itself.
- ✔️ Linux-PAM >= 1.5.3 will use systemd-logind instead of utmp if compiled against systemd >= 254
- libpam.so internal getlogin() replacement.
- setutent()/getutline()/endutent() got replaced with getlogin() with 1.5.3
- pam_issue.so counts how many users are currently logged in.
- setutent()/getutent()/endutent() replaced with sd_get_sessions()
- pam_lastlog.so duplicate functionality, every login app does the same. Deprecated and disabled by default
- logwtmp()
- /var/log/btmp
- /var/log/lastlog
- pam_limits.so counts how often the user is currently logged in.
- setutent()/getutline()/endutent() replaced with sd_get_sessions() and sd_uid_get_login_time()
- pam_timestamp.so/pam_timestamp_check (uses utmp entries to get time of login)
- setutent()/getutent_r()/endutent()
- pam_unix.so
- pam_modutil_getlogin()
- pam_wheel.so
- pam_modutil_getlogin()
- pam_xauth.so
- pam_modutil_getlogin()
- libpam.so internal getlogin() replacement.
- ✅ ppp
- pppd adds and removes utmp and wtmp entries with logwtmp(). Can be replaced with PAM modules for logind, lastlog2 and wtmpdb.
- logwtmp()
- /run/utmp
- /var/log/wtmp
- pppd adds and removes utmp and wtmp entries with logwtmp(). Can be replaced with PAM modules for logind, lastlog2 and wtmpdb.
- ✔️ procps >= 4.04
- libprocps.so.8 counts logged in users
- setutent()/getutent()/endutent()
- w uses nearly all fields for w output
- setutxent()/getutxent()/endutxent()
- libprocps.so.8 counts logged in users
- ✔️ python-psutil creates a list of users with tty, hostname, timestamp and process PID
- setutent()/getutent()/endutent()
- ❌ qemu
- qemu-qa creates a list if users
- setutxent()/getutxent()/endutxen()
- qemu-qa creates a list if users
- ✔️ rsyslog - patch exists
- rsyslogd loops over all users and sends wall message
- setutent()/getutent()/endutent()
- rsyslogd loops over all users and sends wall message
- ✔️ samba
- libsmbd-base-samba4.so creates utmp and wtmp entries, no consumer. PAM modules should be good enough.
- utmpname()
- utmpxname()
- updwtmp()
- updwtmpx()
- setutent()/pututline()/endutent()
- setutxent()/pututxline()/endutxent()
- getutmpx()
- /run/utmp
- /var/log/wtmp
- rpcd_classic creates list of names of all users logged in
- getutxent()
- endutxent()
- libsmbd-base-samba4.so creates utmp and wtmp entries, no consumer. PAM modules should be good enough.
- ❌ screen creates utmp entries
- setutent()
- getutline()
- pututline()
- /run/utmp
- ✅ sddm - can be replaced with PAM modules for logind, lastlog2 and wtmpdb.
- sddm-helper creates utmp/wtmp entries
- setutxent()/endutxent()
- pututxline()
- updwtmpx()
- sddm-helper creates utmp/wtmp entries
- ✅ sessreg - doesn't seems to be needed anymore
- sessreg
- utmpxname()
- setutxent()/endutxent()
- getutxent()
- getutxid()
- pututxline()
- updwtmpx()
- /run/utmp
- /var/log/wtmp
- /var/log/lastlog
- sessreg
- ✔️ shadow >= 4.14.0-rc1
- lastlog reads/displays/modifies lastlog file
- /var/log/lastlog
- login
- setutent(), getutent(), endutent()
- pututline()
- updwtmp()
- /var/log/wtmp
- /var/log/lastlog
- su counts how often the user is already logged in (limits.c/check_logins).
- setutent(), getutent(), endutent()
- logoutd
- setutent(), getutent(), endutent()
- useradd reset old lastlog entries for new users or creates new entry
- /var/log/lastlog
- usermod copies old entry to new entry or creates new entry
- /var/log/lastlog
- lastlog reads/displays/modifies lastlog file
- ✅ sudo - can be solved with PAM modules for logind, lastlog2 and wtmpdb
- setutxent()/getutxline()/endutxent()
- pututxline()
- ✔️ systemd >= v254. Uses utmp for wall messages and to store the compat runlevel. Can be disabled with wtmpdb and if all other tools use logind instead of utmp.
- utmpxname()
- setutxent()
- getutxent()
- getutxid()
- pututxline()
- endutxent()
- updwtmpx()
- /run/utmp
- /var/log/wtmp
- ❌ sysvinit Can be dropped, systemd does not write runlevel at least on openSUSE anymore
- startproc gets runlevel out of utmp file
- utmpname()
- setutent()/getutent()/endutent()
- /run/utmp
- startproc gets runlevel out of utmp file
- ❌ tcsh has an own who implementation.
- setutxent()/getutxent()/endutxent()
- /run/utmp
- ✅ utempter update utmp database. Can be dropped, as there will be no utmp and wtmp file anymore.
- setutent()
- pututline()
- endutent()
- updwtmp()
- /var/log/wtmp
- ✔️ util-linux >= 2.40 will contain the necessary changes
- agetty updates utmp entry and prints number of logged in users
- utmpxname()
- setutxent()/getutxent()/endutxent()
- pututxline()
- updwtmpx()
- /var/log/wtmp
- last shows last valid and failed logins
- /var/log/btmp
- /var/log/wtmp
- login updates utmp and wtmp, creates btmp and lastlog entries
- setutxent()/getutxent()/endutxent()
- getutxid()
- getutxline()
- pututxline()
- updwtmpx()
- utmpxname()
- /var/log/btmp
- /var/log/lastlog
- /var/log/wtmp
- lslogins reads all possible files and gives a consolidate view
- utmpxname()
- getutxent()/endutxent()
- /var/log/btmp
- /var/log/lastlog
- /var/log/wtmp
- runuser writes btmp entry
- updwtmpx()
- /var/log/btmp
- script adds/removes utmp entry
- libutempter
- su writes btmp entry
- updwtmpx()
- /var/log/btmp
- wall print message on all login TTYs
- getutxent()/endutxent()
- write checks if user/tty combination is valid, iterates over all used TTYs to find best matching one
- utmpxname()
- setutxent()/getutxent()/endutxent()
- /run/utmp
- agetty updates utmp entry and prints number of logged in users
- :white_green_mark: vsftpd
- vsftpd updates utmp/wtmp entries, should be done with pam_systemd/pam_wtmpdb
- setutxet()/endutxent()
- pututxline()
- updwtmpx()
- vsftpd updates utmp/wtmp entries, should be done with pam_systemd/pam_wtmpdb
- ✅ xterm
- xterm uses utempter to create utmp entry, not needed.
- libutempter
- xterm uses utempter to create utmp entry, not needed.
- ✔️ joe
- login_tty()
- ✔️ xen
- login_tty()
The files utmp/wtmp are mostly used for:
- who is logged in and on which device
- determine TTYs where wall messages should be send (not only wall, but also systemd and others)
- determine boot time (could be extracted from journald?)
- determine runlevel (is this really from relevance with systemd?)
Who is logged in and on which device is mostly for informative reasons at login time or by tools like uptime and could be dropped.
Quering for the runlevel does not work with systemd anyways.
Leaves tools like wall or systemd, which prints messages e.g. at shutdown time to all logged in users.
btmp
could be dropped, contains a random list of user names from mostly brute force attacks which could also be found in journald.
lastlog
could be dropped, too. Often big file (ok, with holes if the filesystem supports that) and the same informations could be collected from wtmp or from journald.
It looks like the glibc developers don't want to solve this problem but instead deprecate the utmp.h/utmpx.h/lastlog.h interface. Some references to patches and dicussions about this topic:
- [PATCH 16/52] login: Move gnu utmpx to default implementation
- [PATCH 22/52] login: Use 64-bit time on struct lastlog [BZ #25844]
- utmp/wtmp locking allows non-privileged user to deny service
- 64-bit time_t and __WORDSIZE_TIME64_COMPAT32
musl libc does not support utmp/wtmp/btmp/lastlog at all, this are just dummy functions. On alpine, uptime
and who
gives partly wrong values back (e.g. 0 active users or not output at all), w
, wall
and similar tools are missing.
utmps provides the utmpx.h
interface and works as daemon. Could be a solution, but requires that all code get's adjusted to only access utmp/wtmp via utmpx.h
functions and not by reading/writing the file directly. And all code using utmp.h
needs to be adjusted to use utmpx.h
. utmpx.h
does not include functions like e.g. login()/logout().
Additional, the daemon is written for s6-ipcserver
and not systemd
.
Most information which are still used/needed can be obtained from logind/loginctl. Rewrite the corresponding tools to query logind for number of users and the TTYs.
We know:
- Number of logged in users
- Terminal name
- Remote IP address as e.g. used by w and who
This should be enough for most tools.
Missing:
- TTY names of xterm and similar tools.
- Maybe this could be found by parsing
/dev/pts/
? - As this is only needed for broadcast messages, maybe we can use a Desktop API for this and not SPAM the user on all terminal windows with the message?
- Maybe this could be found by parsing
- openssh does not know if a tty is required or not when it calls PAM, but only write thems into
/run/utmp
afterwards. Maybe we can replace the later utmp write with sending the data to logind? - Dead processes. Is this really used today? I could find code writing it or displaying it, but not doing anything with this.
- NEW_TIME/OLD_TIME time entries.
who
could show NEW_TIME entries, but the author wrote:I've never seen one of these, so I don't know what it should look like :^)
.adjtimex
seems to use NEW_TIME. - INIT_PROCESS/LOGIN_PROCESS doesn't seem to be used with systemd, so we can ignore them.
- PID of login process, seems to be only used by
who
.
Replace wtmp with journald. Instead of writing and updating /var/log/wtmp
, log structured data with all necessary informations to journald. Tools like last
will then query the journal and build the entries in the format as if they would get them from wtmp. It's slower than having a dedicated file for this, but good enough for the use cases to replace wtmp.
- Create PAM module to write wtmp entry to journald to enable all applications without the need to patch them all.
Since very few tools support /var/log/lastlog
, the data is of limited use.
From a Desktop system:
localhost:~ # lastlog -u kukuk
Username Port From Latest
kukuk tty1 Mo Dez 19 07:35:24 +0100 2022
But who reports:
localhost:~ # who
kukuk tty7 Jan 6 21:46 (:0)
There is a new implementation lastlog2, which provides a library liblastlog2.so
with a Y2038 secure API on all architectures (32bit and 64bit). This also allows to change the database to e.g. another format without the need to touch any application using it itself. Additional, there will be a PAM module pam_lastlog2.so
and a binary lastlog2
.
The PAM module allows to use this functionality in any PAM aware application. Today every application used for login and authentication uses PAM. The lastlog2
binary will replace lastlog and allows to import that old /var/log/lastlog
file, to maintain the data and of course to show the last login data.
Some special of util-linux and openssh, not used by anything else. Write an entry into the journal and drop that file. It's from no use, since not widely supported, but only by two tools.
- Convert all applications to fetch the data from logind, not utmp. Keep writing to
utmp
andwtmp
. - Create interface to write wtmp entries to
wtmp
andjournald
. - Read wtmp data from
journald
. - Disable writing of
utmp
andwtmp
.
In parallel:
- Disable
lastlog
.
- systemd
- Linux-PAM ✔️
- shadow
- util-linux