Skip to content

Latest commit

 

History

History
450 lines (389 loc) · 21.2 KB

Y2038.md

File metadata and controls

450 lines (389 loc) · 21.2 KB

Y2038 and utmp/wtmp/lastlog on bi-arch systems like x86-64

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.

API

Headers

  • utmp.h, bits/utmp.h
  • utmpx.h, bits/utmpx.h
  • lastlog.h

Functions

From glibc:

Safe functions partly using utmp/wtmp internal:

Part of utmp.h but not using utmp/wtmp/lastlog:

Filenames

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

Projects depending on utmp/wtmp/btmp/lastlog

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.

Depending on utmp/wtmp/btmp/lastlog directly

  • 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
  • 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
  • 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
  • ✔️ 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()
  • ✔️ monitoring-plugins > 2.3.3 (PR:check_users: prefer systemd-logind over utmp)
    • check_users counts number of logged in users
      • setutxent()/getuxent()/endutxent()
  • ❌ net-snmp
    • libnetsnmpmibs.40.2.0 counts number of logged in users, can be replaced with sd_get_sessions(NULL)
      • setutxent()/getutxent()/endutxent()
  • ✔️ 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)
  • ✔️ 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()
  • 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
  • ✔️ procps >= 4.04
    • libprocps.so.8 counts logged in users
      • setutent()/getutent()/endutent()
    • w uses nearly all fields for w output
      • setutxent()/getutxent()/endutxent()
  • ✔️ 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()
  • ✔️ rsyslog - patch exists
    • rsyslogd loops over all users and sends wall message
      • setutent()/getutent()/endutent()
  • ✔️ 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()
  • ❌ 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()
  • sessreg - doesn't seems to be needed anymore
    • sessreg
      • utmpxname()
      • setutxent()/endutxent()
      • getutxent()
      • getutxid()
      • pututxline()
      • updwtmpx()
      • /run/utmp
      • /var/log/wtmp
      • /var/log/lastlog
  • ✔️ 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
  • ✅ 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
  • 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
  • :white_green_mark: vsftpd
    • vsftpd updates utmp/wtmp entries, should be done with pam_systemd/pam_wtmpdb
      • setutxet()/endutxent()
      • pututxline()
      • updwtmpx()
  • ✅ xterm
    • xterm uses utempter to create utmp entry, not needed.
      • libutempter

Using utmp.h functions, but do not depend on utmp/wtmp/...

  • ✔️ joe
    • login_tty()
  • ✔️ xen
    • login_tty()

Usage Summary

utmp/wtmp usage

The files utmp/wtmp are mostly used for:

  1. who is logged in and on which device
  2. determine TTYs where wall messages should be send (not only wall, but also systemd and others)
  3. determine boot time (could be extracted from journald?)
  4. 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/lastlog

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.

Upstream status and solutions

glibc

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:

musl libc

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

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.

Ideas for solutions

utmp

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?
  • 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.

wtmp

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.

lastlog

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.

btmp

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.

Action Plan

  1. Convert all applications to fetch the data from logind, not utmp. Keep writing to utmp and wtmp.
  2. Create interface to write wtmp entries to wtmp and journald.
  3. Read wtmp data from journald.
  4. Disable writing of utmp and wtmp.

In parallel:

  1. Disable lastlog.

Upstream Issues and PRs

Blogs