From 93b7d20dcc9cad3beba945aae264d63e68069953 Mon Sep 17 00:00:00 2001 From: Vitalii Popov Date: Sat, 26 Sep 2026 19:04:39 +0300 Subject: [PATCH] revert changes to apply with system where kernel is 4.19 --- ANCIENT-MOD.md | 80 ++++++++++++++++ src/basic/cgroup-util.c | 17 ++++ src/basic/cgroup-util.h | 1 + src/basic/fd-util.c | 96 ++++++++++++++++---- src/basic/fd-util.h | 1 + src/basic/fs-util.c | 30 +++++- src/basic/mountpoint-util.c | 2 + src/basic/pidfd-util.c | 12 ++- src/basic/pidref.c | 35 +++++-- src/basic/socket-util.c | 14 ++- src/basic/stat-util.c | 111 +++++++++++++++++++++++ src/core/cgroup.c | 45 ++++++++- src/core/exec-credential.c | 5 +- src/core/namespace.c | 32 ++++++- src/core/unit.c | 2 +- src/libsystemd/sd-event/sd-event.c | 60 +++++++++--- src/libsystemd/sd-netlink/netlink-util.c | 3 +- src/login/pam_systemd.c | 2 +- src/network/networkd-link.c | 45 ++++++++- src/network/networkd-manager.c | 7 +- src/shared/firewall-util.c | 12 +++ src/shared/killall.c | 3 + src/shared/loop-util.c | 16 ++++ src/shared/mount-setup.c | 35 ++++--- src/test/test-fd-util.c | 10 +- 25 files changed, 600 insertions(+), 76 deletions(-) create mode 100644 ANCIENT-MOD.md diff --git a/ANCIENT-MOD.md b/ANCIENT-MOD.md new file mode 100644 index 0000000000..98394c9132 --- /dev/null +++ b/ANCIENT-MOD.md @@ -0,0 +1,80 @@ +# ANCIENT-MOD: running on old kernels (target: 4.19) + +Upstream requires kernel ≥ 5.10 (see `README`, "REQUIREMENTS") and dropped the fallback code for +older kernels. This branch restores or adds fallbacks so the system can at least boot on 4.19. +Code touched by this effort is marked with `ANCIENT-MOD` comments where practical. + +Legend: **revert** = reverse-applied an upstream commit (possibly adjusted), **new** = new code +because upstream code was refactored since the fallback was removed. + +## Boot-critical + +| Area | Kernel feature | Change | Where | +|---|---|---|---| +| Mount point checks (`is_mount_point_at()`, `path_is_mount_point()`), used by early `mount_setup()` for `/proc`, `/sys`, `/dev` with `MNT_FATAL` | `STATX_ATTR_MOUNT_ROOT` (5.8) | **new**: `xstatx_full()` emulates the attribute when it is required but missing: compares the mount id (from `/proc/self/fdinfo`) of the inode with its parent's (`..` for directories, parent path for others); same inode as parent ⇒ root ⇒ mount point. Replaces upstream fallback removed in `a98a6eb95c`. | `src/basic/stat-util.c` (`xstatx_emulate()`) | +| `chase()` (used everywhere), `path_get_mnt_id_at()`, `fds_inode_and_mount_same()`, `XSTATX_MNT_ID_BEST` | `STATX_MNT_ID` (5.8/5.10) | **new**: same emulation fills `stx_mnt_id` from `/proc/self/fdinfo/` `mnt_id:` (available since 3.15). Replaces removals in `7ecfa87c56`, `e5a3127339`. | `src/basic/stat-util.c` | +| cgroup2 mount | `memory_recursiveprot` (5.7) | **revert** (adjusted) of `80c5b63db5`: option is probed again instead of always passed; if it cannot be probed because `fsopen()` is missing (< 5.2) it is not used. | `src/shared/mount-setup.c` | +| `mount_option_supported()` | `fsopen()` (5.2) | **revert** of part of `c2198d0c3f`: return `-EAGAIN` ("don't know") when the new mount API is unavailable. | `src/basic/mountpoint-util.c` | +| `close_all_fds*()` (every fork/exec, `systemd-executor`) | `close_range()` (5.9) | **revert** of `c471cca093` and `d58c826c54`: fall back to iterating `/proc/self/fd` (or the frugal loop). | `src/basic/fd-util.c`, `src/basic/fd-util.h`, `src/test/test-fd-util.c` | +| `pidref_set_pid()` (every process PID1 tracks, `safe_fork()`, `posix_spawn_wrapper()`) | `pidfd_open()` (5.3) | **revert** (adjusted) of part of `6e14c46bac`: ENOSYS ⇒ PidRef without fd (plain PID). | `src/basic/pidref.c` | +| `pidref_wait_for_terminate_full()` with timeout | pidfd (5.3) | **new**: without pidfd poll `waitid(P_PID, …, WNOHANG)` every 10 ms instead of returning `-ENOMEDIUM`. | `src/basic/pidref.c` | +| `pidfd_check_pidfs()`, `pidfd_info_mask_is_supported()`, `pidfd_get_inode_id_self_cached()` | pidfd (5.3) | **revert**/new: ENOSYS ⇒ "not supported" instead of hard error. | `src/basic/pidfd-util.c` | +| sd-event child sources (`sd_event_add_child()`, used by udevd, logind, …) | pidfd (5.3), `waitid(P_PIDFD)` (5.4) | **new** (upstream removed the non-pidfd path in `6e14c46bac` and later made the code pidfd-only): if `pidfd_open()` is unsupported the source works on the PID via `waitid(P_PID)` + SIGCHLD signalfd. SIGCHLD is blocked automatically in that case (upstream no longer requires callers to block it). `send_child_signal()` falls back to `kill()`/`rt_sigqueueinfo()`; `set_child_pidfd_own()` returns `-EOPNOTSUPP` without pidfd. | `src/libsystemd/sd-event/sd-event.c` | +| `pam_systemd` session creation | pidfd (5.3) | **revert** of part of `6e14c46bac`: fall back to `CreateSession` (PID based). | `src/login/pam_systemd.c` | +| Unit cgroup-empty tracking (`unit_check_cgroup_events()`) | `frozen` key in `cgroup.events` (5.2) | **new**: if the key is missing, read only `populated`. Without this every cgroup event read failed and stopped units were never noticed as empty. | `src/core/cgroup.c` | +| Freeze/thaw | cgroup v2 freezer (5.2) | **revert** of `3c86a935ad`: `cg_freezer_supported()` restored; freeze is `-EOPNOTSUPP` / no-op when `cgroup.freeze` is absent. | `src/basic/cgroup-util.[ch]`, `src/core/cgroup.c`, `src/core/unit.c` | +| Credentials (`LoadCredential=`, `SetCredential=`, `ImportCredential=` — used by many stock units) | new mount API `fsopen`/`fsmount`/`move_mount` (5.2) | **new**: if the credentials fs cannot be created because the API is missing, use the existing "plain directory" fallback (previously only used on EPERM). | `src/core/exec-credential.c` | +| `ProtectProc=` / `ProcSubset=` (used by logind, networkd, timesyncd, hostnamed, …) | per-instance procfs, textual `hidepid=`, `subset=` (5.8) | **new**: on kernels < 5.8 the options are ignored (they would either fail with EINVAL or, worse, change the host's shared `/proc` superblock). `/proc/sys` & friends are then still protected by the normal ProtectKernel* mounts. Upstream removed the old fallback in `0cba5bdcf1`. | `src/core/namespace.c` | +| `RootDirectory=` | `open_tree()` (5.2) | **new**: tolerate ENOSYS when cloning the root directory mount and use the classic bind mount path. | `src/core/namespace.c` | +| `futimens_opath()`, `access_fd()` | `AT_EMPTY_PATH` for `utimensat()`/`faccessat()` (5.8) | **revert** of `52bf232e82` (manually): fall back to `/proc/self/fd/`. | `src/basic/fs-util.c` | + +## Networking + +| Area | Kernel feature | Change | Where | +|---|---|---|---| +| `socket_bind_to_ifindex()` (DHCP client, resolved, networkd, …) | `SO_BINDTOIFINDEX` (5.1) | **revert** of `ddbbc98bd5` (manually): fall back to `SO_BINDTODEVICE`. | `src/basic/socket-util.c` | +| `rtnl_resolve_ifname_full()` (alternative names) | `IFLA_ALT_IFNAME` lookup (5.5) | **revert** of `42b09813f4`: treat EINVAL like ENODEV. | `src/libsystemd/sd-netlink/netlink-util.c` | +| networkd startup enumeration | nexthop (5.3) | **revert** of `034b69d8b8` (manually): ignore `-EOPNOTSUPP` from nexthop enumeration, otherwise networkd refuses to start. | `src/network/networkd-manager.c` | +| networkd permanent MAC address | `IFLA_PERM_ADDRESS` (5.6) | **revert** of `04f05d9bd7`: fall back to ethtool. | `src/network/networkd-link.c` | +| IPv6 DNAT (nspawn port forwarding / firewall-util) | nft concatenation fix (5.8) | **revert** of `58fb46ecd5` (manually): `-EOVERFLOW` ⇒ `-EOPNOTSUPP`. | `src/shared/firewall-util.c` | + +## Other + +| Area | Kernel feature | Change | Where | +|---|---|---|---| +| `SurviveFinalKillSignal=` | `user.*` xattrs on cgroupfs (5.7) | **revert** of `8292b859f3`: fall back to `trusted.survive_final_kill_signal`. | `src/core/cgroup.c`, `src/shared/killall.c` | +| Loop devices via `LOOP_SET_STATUS64` fallback | buffer cache invalidation bug (fixed in 5.0) | **revert** of `5e3887e6c1`: issue `BLKFLSBUF` when offset/size is set. | `src/shared/loop-util.c` | + +## Known remaining gaps (not addressed) + +- **cgroup v1 is gone upstream**: the kernel must boot with a cgroup v2 hierarchy. 4.19's cgroup v2 + lacks the `cpuset` (5.0) and `freezer` (5.2) controllers, `cgroup.kill` (5.14, already graceful). +- **PSI** (4.20): `systemd-oomd` does not work; memory-pressure event sources are already optional. +- **BPF**: features needing BPF links / BPF LSM / bounded loops (5.3–5.7) — `RestrictFileSystems=`, + `RestrictNetworkInterfaces=`, `SocketBind*=`, `BindNetworkInterface=`, … — report "not supported" + (upstream probing, `ef12eadfc8`). +- **New mount API without fallback** (5.2/5.12): `PrivateBPF=`, `ExtensionImages=`/`ExtensionDirectories=` + (overlay via `fsopen`), `MountImages=`, runtime `systemctl bind`/`mount-image`, idmapped mounts, + mountfsd, nsresourced, sysext/confext, homed (also needs fscrypt v2 / 5.4), coredump's mount of + the crashed process' root. +- **`MS_NOSYMFOLLOW`** (5.10) is passed as a legacy `mount(2)` flag; older kernels silently ignore + unknown flags, so the mount just lacks `nosymfollow`. +- **Credentials** use a plain directory instead of a dedicated (noswap) tmpfs, i.e. less isolation. +- **Per-device IO weight** (`IODeviceWeight=`, BFQ per-device weight, 5.4): upstream dropped the + EINVAL workaround (`00c4851af0`), expect warnings when used. +- **udev net_id** for IP-over-InfiniBand (`dev_port`, 4.20): `d3d375a686` not reverted; multi-port + IPoIB devices may get colliding names. +- `test-event`'s `test_pidfd` inherently requires pidfds and fails without them. +- Anything else newer than 5.10 listed in `README` as "recommended baseline" is expected to fall + back gracefully upstream, but that has not been verified on a real old kernel. + +## Testing done + +- Full native test suite (`meson test -C build`) on a 7.x kernel. +- Selected tests (`test-stat-util`, `test-mountpoint-util`, `test-chase`, `test-fs-util`, + `test-fd-util`, `test-pidref`, `test-process-util`, `test-socket-util`, `test-mount-util`, + `test-udev-spawn`, `test-async`, `test-event` except `test_pidfd`) and `systemd --test --system` + (with the built generators) under an `LD_PRELOAD` shim that simulates an old kernel: strips + `STATX_MNT_ID*`/`STATX_ATTR_MOUNT_ROOT` from `statx()` results and makes `pidfd_open`, + `pidfd_send_signal`, `pidfd_spawn`, `close_range` and the new mount API return ENOSYS. +- **Not** booted on a real 4.19 kernel yet. diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index aac0f035eb..4111954bc4 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -206,6 +206,23 @@ bool cg_kill_supported(void) { return (supported = false); } +/* ANCIENT-MOD: cgroup v2 freezer is available since kernel 5.2 */ +bool cg_freezer_supported(void) { + static thread_local int supported = -1; + + if (supported >= 0) + return supported; + + if (cg_is_available() <= 0) + return (supported = false); + + if (access("/sys/fs/cgroup/init.scope/cgroup.freeze", F_OK) >= 0) + return (supported = true); + if (errno != ENOENT) + log_debug_errno(errno, "Failed to check whether cgroup freezer is available, assuming not: %m"); + return (supported = false); +} + int cg_enumerate_subgroups(const char *path, DIR **ret) { _cleanup_free_ char *fs = NULL; DIR *d; diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h index f53a1c9390..1b25b03f64 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -235,6 +235,7 @@ int cg_mask_from_string(const char *s, CGroupMask *ret); int cg_mask_to_string(CGroupMask mask, char **ret); bool cg_kill_supported(void); +bool cg_freezer_supported(void); DECLARE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController); diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index c6e78c59b6..3ddcabea9a 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -260,9 +260,10 @@ int close_all_fds_frugal(const int except[], size_t n_except) { assert(except || n_except == 0); - /* This is the inner fallback core of close_all_fds(). This never calls malloc() or so and hence is - * safe to be called in signal handler context. Most users should call close_all_fds(), but when we - * assume we are called from signal handler context, then use this simpler call instead. */ + /* This is the inner fallback core of close_all_fds(). This never calls malloc() or opendir() or so + * and hence is safe to be called in signal handler context. Most users should call close_all_fds(), + * but when we assume we are called from signal handler context, then use this simpler call + * instead. */ max_fd = get_max_fd(); if (max_fd < 0) @@ -288,6 +289,44 @@ int close_all_fds_frugal(const int except[], size_t n_except) { return r; } +int close_all_fds_by_proc(const int except[], size_t n_except) { + _cleanup_closedir_ DIR *d = NULL; + int r = 0; + + d = opendir("/proc/self/fd"); + if (!d) + return close_all_fds_frugal(except, n_except); /* ultimate fallback if /proc/ is not available */ + + FOREACH_DIRENT(de, d, return -errno) { + int fd = -EBADF, q; + + if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN)) + continue; + + fd = parse_fd(de->d_name); + if (fd < 0) + /* Let's better ignore this, just in case */ + continue; + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + if (fd_in_set(fd, except, n_except)) + continue; + + q = close_nointr(fd); + if (q != -EBADF) /* Valgrind has its own FD and doesn't want to have it closed */ + RET_GATHER(r, q); + } + + return r; +} + +static bool have_close_range = true; /* Assume we live in the future */ + static int close_all_fds_special_case(const int except[], size_t n_except) { assert(n_except == 0 || except); @@ -295,6 +334,9 @@ static int close_all_fds_special_case(const int except[], size_t n_except) { * nicely, since we won't need sorting for them. Returns > 0 if the special casing worked, 0 * otherwise. */ + if (!have_close_range) + return 0; + if (n_except == 1 && except[0] < 0) /* Minor optimization: if we only got one fd, and it's invalid, * we got none */ n_except = 0; @@ -303,22 +345,31 @@ static int close_all_fds_special_case(const int except[], size_t n_except) { case 0: /* Close everything. Yay! */ - if (close_range(3, INT_MAX, 0) < 0) - return -errno; - return 1; + if (close_range(3, INT_MAX, 0) >= 0) + return 1; + + if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) { + have_close_range = false; + return 0; + } + + return -errno; case 1: /* Close all but exactly one, then we don't need no sorting. This is a pretty common * case, hence let's handle it specially. */ - if (except[0] > 3 && close_range(3, except[0] - 1, 0) < 0) - return -errno; + if ((except[0] <= 3 || close_range(3, except[0]-1, 0) >= 0) && + (except[0] >= INT_MAX || close_range(MAX(3, except[0]+1), -1, 0) >= 0)) + return 1; - if (except[0] < INT_MAX && close_range(MAX(3, except[0] + 1), -1, 0) < 0) - return -errno; + if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) { + have_close_range = false; + return 0; + } - return 1; + return -errno; default: return 0; @@ -350,6 +401,9 @@ int close_all_fds(const int except[], size_t n_except) { if (r > 0) /* special case worked! */ return 0; + if (!have_close_range) + return close_all_fds_by_proc(except, n_except); + _cleanup_free_ int *sorted_malloc = NULL; size_t n_sorted; int *sorted; @@ -369,7 +423,7 @@ int close_all_fds(const int except[], size_t n_except) { sorted = newa(int, n_sorted); if (!sorted) /* Fallback on OOM. */ - return close_all_fds_frugal(except, n_except); + return close_all_fds_by_proc(except, n_except); memcpy(sorted, except, n_except * sizeof(int)); @@ -391,8 +445,13 @@ int close_all_fds(const int except[], size_t n_except) { continue; /* Close everything between the start and end fds (both of which shall stay open) */ - if (close_range(start + 1, end - 1, 0) < 0) - return -errno; + if (close_range(start + 1, end - 1, 0) < 0) { + if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) + return -errno; + + have_close_range = false; + return close_all_fds_by_proc(except, n_except); + } } /* The loop succeeded. Let's now close everything beyond the end */ @@ -400,8 +459,13 @@ int close_all_fds(const int except[], size_t n_except) { if (sorted[n_sorted-1] >= INT_MAX) /* Dont let the addition below overflow */ return 0; - if (close_range(sorted[n_sorted-1] + 1, INT_MAX, 0) < 0) - return -errno; + if (close_range(sorted[n_sorted-1] + 1, INT_MAX, 0) < 0) { + if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) + return -errno; + + have_close_range = false; + return close_all_fds_by_proc(except, n_except); + } return 0; } diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 999b49ab03..bee6fdcae3 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -127,6 +127,7 @@ int get_max_fd(void); int close_all_fds(const int except[], size_t n_except); int close_all_fds_without_malloc(const int except[], size_t n_except); +int close_all_fds_by_proc(const int except[], size_t n_except); int close_all_fds_frugal(const int except[], size_t n_except); int pack_fds(int fds[], size_t n); diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index d68a09814f..c3e2f24fc8 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -322,7 +322,20 @@ int futimens_opath(int fd, const struct timespec ts[2]) { assert(fd >= 0); - return RET_NERRNO(utimensat(fd, "", ts, AT_EMPTY_PATH)); + if (utimensat(fd, "", ts, AT_EMPTY_PATH) >= 0) + return 0; + if (errno != EINVAL) + return -errno; + + /* ANCIENT-MOD: AT_EMPTY_PATH is supported since kernel 5.8, fall back to /proc/self/fd/ */ + if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, /* flags= */ 0) < 0) { + if (errno != ENOENT) + return -errno; + + return proc_fd_enoent_errno(); + } + + return 0; } int stat_warn_permissions(const char *path, const struct stat *st) { @@ -371,7 +384,20 @@ int access_fd(int fd, int mode) { assert(fd >= 0); - return RET_NERRNO(faccessat(fd, "", mode, AT_EMPTY_PATH)); + if (faccessat(fd, "", mode, AT_EMPTY_PATH) >= 0) + return 0; + if (errno != EINVAL) + return -errno; + + /* ANCIENT-MOD: AT_EMPTY_PATH is supported since kernel 5.8, fall back to /proc/self/fd/ */ + if (access(FORMAT_PROC_FD_PATH(fd), mode) < 0) { + if (errno != ENOENT) + return -errno; + + return proc_fd_enoent_errno(); + } + + return 0; } int touch_fd(int fd, usec_t stamp) { diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index 49808cc962..bc3a819908 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -681,6 +681,8 @@ int mount_option_supported(const char *fstype, const char *key, const char *valu if (fd < 0) { if (errno == ENODEV) return log_debug_errno(errno, "Kernel does not support file system type '%s'.", fstype); + if (ERRNO_IS_NOT_SUPPORTED(errno)) + return -EAGAIN; /* ANCIENT-MOD: new mount API not available (kernel < 5.2) → don't know */ return log_debug_errno(errno, "Failed to open superblock context for '%s': %m", fstype); } diff --git a/src/basic/pidfd-util.c b/src/basic/pidfd-util.c index f15235f5c2..7d1a91b6e7 100644 --- a/src/basic/pidfd-util.c +++ b/src/basic/pidfd-util.c @@ -28,8 +28,11 @@ int pidfd_check_pidfs(int pid_fd) { _cleanup_close_ int our_fd = -EBADF; if (pid_fd < 0) { our_fd = pidfd_open(getpid_cached(), /* flags= */ 0); - if (our_fd < 0) + if (our_fd < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno)) /* ANCIENT-MOD: kernel < 5.3 */ + return (have_pidfs = false); return -errno; + } pid_fd = our_fd; } @@ -102,6 +105,8 @@ int pidfd_info_mask_is_supported(uint64_t mask) { if (cached == 0) { _cleanup_close_ int pidfd = r = RET_NERRNO(pidfd_open(getpid_cached(), /* flags= */ 0)); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) /* ANCIENT-MOD: kernel < 5.3 */ + return (cached = -EOPNOTSUPP); if (r < 0) return r; @@ -334,8 +339,11 @@ int pidfd_get_inode_id_self_cached(uint64_t *ret) { return initialized; _cleanup_close_ int fd = pidfd_open(getpid_cached(), 0); - if (fd < 0) + if (fd < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno)) /* ANCIENT-MOD: kernel < 5.3 */ + return (initialized = -EOPNOTSUPP); return -errno; + } r = pidfd_get_inode_id(fd, &cached); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) diff --git a/src/basic/pidref.c b/src/basic/pidref.c index ff52ccc311..28b698745f 100644 --- a/src/basic/pidref.c +++ b/src/basic/pidref.c @@ -106,9 +106,10 @@ int pidref_set_pid_full(PidRef *pidref, pid_t pid, unsigned flags) { fd = pidfd_open(pid, flags); if (fd < 0) { - /* Graceful fallback in case the kernel is out of fds. + /* Graceful fallback in case the kernel is out of fds or lacks pidfd_open() (ANCIENT-MOD: < 5.3). * The PIDFD_THREAD flag is supported since kernel v6.9 (64bef697d33b75fc06c5789b3f8108680271529f). */ - if (!(ERRNO_IS_RESOURCE(errno) || (errno == EINVAL && FLAGS_SET(flags, PIDFD_THREAD)))) + if (!(ERRNO_IS_RESOURCE(errno) || ERRNO_IS_NOT_SUPPORTED(errno) || + (errno == EINVAL && FLAGS_SET(flags, PIDFD_THREAD)))) return log_debug_errno(errno, "Failed to open pidfd for pid " PID_FMT ": %m", pid); fd = -EBADF; @@ -468,16 +469,34 @@ int pidref_wait_for_terminate_full(PidRef *pidref, usec_t timeout, siginfo_t *re if (pidref->pid == 1 || pidref_is_self(pidref)) return -ECHILD; - if (pidref->fd < 0 && (timeout != USEC_INFINITY || fiber_ops_is_set())) - return -ENOMEDIUM; - usec_t ts = timeout == USEC_INFINITY ? USEC_INFINITY : usec_add(now(CLOCK_MONOTONIC), timeout); + /* ANCIENT-MOD: no pidfd (kernel < 5.3), poll with waitid(WNOHANG) to honour the timeout */ + if (pidref->fd < 0 && ts != USEC_INFINITY) + for (;;) { + siginfo_t si = {}; + + r = RET_NERRNO(waitid(P_PID, pidref->pid, &si, WEXITED|WNOHANG)); + if (r == -EINTR) + continue; + if (r < 0) + return r; + if (si.si_pid != 0) { + if (ret_si) + *ret_si = si; + return 0; + } + + if (now(CLOCK_MONOTONIC) >= ts) + return -ETIMEDOUT; + + (void) usleep_safe(10 * USEC_PER_MSEC); + } + /* Poll the pidfd before waitid() if either there's a finite timeout (so we can honor it) or * we're on a fiber (so fd_wait_for_event() can suspend us instead of blocking the event loop - * inside waitid()). Otherwise let waitid() block directly. The precondition above guarantees - * pidref->fd >= 0 in both cases. */ - bool poll_first = ts != USEC_INFINITY || fiber_ops_is_set(); + * inside waitid()). Otherwise let waitid() block directly. */ + bool poll_first = pidref->fd >= 0 && (ts != USEC_INFINITY || fiber_ops_is_set()); for (;;) { if (poll_first) { diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 79eabcf320..a52b54f733 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -1419,13 +1419,25 @@ int socket_bind_to_ifname(int fd, const char *ifname) { } int socket_bind_to_ifindex(int fd, int ifindex) { + char ifname[IF_NAMESIZE]; + int r; + assert(fd >= 0); if (ifindex <= 0) /* Drop binding */ return RET_NERRNO(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, NULL, 0)); - return setsockopt_int(fd, SOL_SOCKET, SO_BINDTOIFINDEX, ifindex); + r = setsockopt_int(fd, SOL_SOCKET, SO_BINDTOIFINDEX, ifindex); + if (r != -ENOPROTOOPT) + return r; + + /* ANCIENT-MOD: fall back to SO_BINDTODEVICE on kernels < 5.1 which didn't have SO_BINDTOIFINDEX */ + r = format_ifname(ifindex, ifname); + if (r < 0) + return r; + + return socket_bind_to_ifname(fd, ifname); } int socket_autobind(int fd, char **ret_name) { diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index e455f41a8c..7a2c5702e2 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -11,14 +11,17 @@ #include "dirent-util.h" #include "errno-util.h" #include "fd-util.h" +#include "fileio.h" #include "filesystems.h" #include "fs-util.h" #include "hash-funcs.h" #include "log.h" #include "mountpoint-util.h" +#include "parse-util.h" #include "path-util.h" #include "siphash24.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-util.h" #include "time-util.h" @@ -413,6 +416,102 @@ static const char* statx_attribute_to_name(uint64_t attr); DEFINE_STATX_BITS_TO_STRING(statx_mask, unsigned, statx_mask_one_to_name, "0x%x"); DEFINE_STATX_BITS_TO_STRING(statx_attributes, uint64_t, statx_attribute_to_name, "0x%" PRIx64); +/* ANCIENT-MOD: emulation of STATX_MNT_ID and STATX_ATTR_MOUNT_ROOT for kernels < 5.8 */ +static int fd_fdinfo_mnt_id(int fd, uint64_t *ret) { + char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; + _cleanup_free_ char *p = NULL; + int r; + + assert(fd >= 0); + assert(ret); + + xsprintf(path, "/proc/self/fdinfo/%i", fd); + + r = get_proc_field(path, "mnt_id", &p); + if (r == -ENOENT) + return proc_fd_enoent_errno(); + if (r == -ENODATA) + return -EUNATCH; + if (r < 0) + return r; + + return safe_atou64(p, ret); +} + +static int fd_open_parent(int fd, const struct statx *sx) { + _cleanup_free_ char *p = NULL, *d = NULL; + int r; + + if (FLAGS_SET(sx->stx_mask, STATX_TYPE) ? S_ISDIR(sx->stx_mode) : fd_verify_directory(fd) >= 0) + return RET_NERRNO(openat(fd, "..", O_PATH|O_DIRECTORY|O_CLOEXEC)); + + r = fd_get_path(fd, &p); + if (r < 0) + return r; + + r = path_extract_directory(p, &d); + if (r < 0) + return r; + + return RET_NERRNO(open(d, O_PATH|O_DIRECTORY|O_CLOEXEC)); +} + +static int xstatx_emulate( + int fd, + const char *path, + int statx_flags, + bool want_mnt_id, + bool want_mount_root, + struct statx *sx) { + + _cleanup_close_ int target_fd = -EBADF, parent_fd = -EBADF; + uint64_t mnt_id, parent_mnt_id; + int r; + + assert(sx); + + if (isempty(path) && fd >= 0) + target_fd = RET_NERRNO(fcntl(fd, F_DUPFD_CLOEXEC, 3)); + else + target_fd = RET_NERRNO(openat(fd, isempty(path) ? "." : path, + O_PATH|O_CLOEXEC|(FLAGS_SET(statx_flags, AT_SYMLINK_NOFOLLOW) ? O_NOFOLLOW : 0))); + if (target_fd < 0) + return target_fd; + + r = fd_fdinfo_mnt_id(target_fd, &mnt_id); + if (r < 0) + return r; + + if (want_mnt_id) { + sx->stx_mnt_id = mnt_id; + sx->stx_mask |= STATX_MNT_ID; + } + + if (!want_mount_root) + return 0; + + bool is_root = false; + parent_fd = fd_open_parent(target_fd, sx); + if (parent_fd < 0) + log_debug_errno(parent_fd, "Failed to open parent to determine mount root, assuming not a mount root: %m"); + else { + struct stat a, b; + + r = fd_fdinfo_mnt_id(parent_fd, &parent_mnt_id); + if (r < 0) + return r; + + if (mnt_id != parent_mnt_id) + is_root = true; + else if (fstat(target_fd, &a) >= 0 && fstat(parent_fd, &b) >= 0) + is_root = stat_inode_same(&a, &b); + } + + SET_FLAG(sx->stx_attributes, STATX_ATTR_MOUNT_ROOT, is_root); + sx->stx_attributes_mask |= STATX_ATTR_MOUNT_ROOT; + return 0; +} + int xstatx_full(int fd, const char *path, int statx_flags, @@ -457,6 +556,18 @@ int xstatx_full(int fd, &sx) < 0) return negative_errno(); + bool want_mnt_id = + (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) && !(sx.stx_mask & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE))) || + (FLAGS_SET(request_mask, STATX_MNT_ID) && !FLAGS_SET(sx.stx_mask, STATX_MNT_ID)); + bool want_mount_root = + FLAGS_SET(mandatory_attributes, STATX_ATTR_MOUNT_ROOT) && + !FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT); + if (want_mnt_id || want_mount_root) { + r = xstatx_emulate(fd, path, statx_flags, want_mnt_id, want_mount_root, &sx); + if (r < 0) + log_debug_errno(r, "Failed to emulate statx() mount fields, ignoring: %m"); + } + if (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) && !(sx.stx_mask & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE))) return log_debug_errno(SYNTHETIC_ERRNO(EUNATCH), "statx() did not return either STATX_MNT_ID or STATX_MNT_ID_UNIQUE."); diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 543d1ac8e3..491855a4ac 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -937,12 +937,39 @@ static void cgroup_delegate_xattr_apply(Unit *u) { } static void cgroup_survive_xattr_apply(Unit *u) { + int r; + assert(u); - if (u->survive_final_kill_signal) - unit_set_xattr_graceful(u, "user.survive_final_kill_signal", "1", 1); - else + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return; + + if (u->survive_final_kill_signal) { + r = cg_set_xattr( + crt->cgroup_path, + "user.survive_final_kill_signal", + "1", + 1, + /* flags= */ 0); + /* user xattr support was added in kernel v5.7 */ + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + r = cg_set_xattr( + crt->cgroup_path, + "trusted.survive_final_kill_signal", + "1", + 1, + /* flags= */ 0); + if (r < 0) + log_unit_debug_errno(u, + r, + "Failed to set 'survive_final_kill_signal' xattr on control " + "group %s, ignoring: %m", + empty_to_root(crt->cgroup_path)); + } else { unit_remove_xattr_graceful(u, "user.survive_final_kill_signal"); + unit_remove_xattr_graceful(u, "trusted.survive_final_kill_signal"); + } } static void cgroup_xattr_apply(Unit *u) { @@ -3189,6 +3216,13 @@ static int unit_check_cgroup_events(Unit *u) { "cgroup.events", STRV_MAKE("populated", "frozen"), values); + if (r == -ENXIO) + /* ANCIENT-MOD: the "frozen" key exists only since kernel 5.2 (cgroup v2 freezer) */ + r = cg_get_keyed_attribute( + crt->cgroup_path, + "cgroup.events", + STRV_MAKE("populated"), + values); if (r < 0) return r; @@ -3203,7 +3237,7 @@ static int unit_check_cgroup_events(Unit *u) { /* Disregard freezer state changes due to operations not initiated by us. * See: https://github.com/systemd/systemd/pull/13512/files#r416469963 and * https://github.com/systemd/systemd/pull/13512#issuecomment-573007207 */ - if (IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_FREEZING_BY_PARENT, FREEZER_THAWING)) + if (values[1] && IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_FREEZING_BY_PARENT, FREEZER_THAWING)) unit_freezer_complete(u, streq(values[1], "0") ? FREEZER_RUNNING : FREEZER_FROZEN); free_many_charp(values, ELEMENTSOF(values)); @@ -4122,6 +4156,9 @@ int unit_cgroup_freezer_action(Unit *u, FreezerAction action) { assert(action >= 0); assert(action < _FREEZER_ACTION_MAX); + if (!cg_freezer_supported()) + return 0; + unit_next_freezer_state(u, action, &next, &objective); CGroupRuntime *crt = unit_get_cgroup_runtime(u); diff --git a/src/core/exec-credential.c b/src/core/exec-credential.c index 9c209404d3..4f4f7d01e1 100644 --- a/src/core/exec-credential.c +++ b/src/core/exec-credential.c @@ -1052,8 +1052,9 @@ static int setup_credentials_internal( } mfd = fsmount_credentials_fs(&fs_fd); - if (ERRNO_IS_NEG_PRIVILEGE(mfd) && !dir_mounted) { - log_debug_errno(mfd, "Lacking privilege to mount credentials fs, falling back to plain directory."); + if ((ERRNO_IS_NEG_PRIVILEGE(mfd) || ERRNO_IS_NEG_NOT_SUPPORTED(mfd)) && !dir_mounted) { + /* ANCIENT-MOD: also fall back if the new mount API is unavailable (kernel < 5.2) */ + log_debug_errno(mfd, "Lacking privilege or kernel support to mount credentials fs, falling back to plain directory."); return setup_credentials_plain_dir(context, cred_dir); } if (mfd < 0) diff --git a/src/core/namespace.c b/src/core/namespace.c index dd15bda3ac..037a8b27d2 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "alloc-util.h" @@ -1562,6 +1563,25 @@ static int mount_private_cgroup2fs(const MountEntry *m, const NamespaceParameter return mount_private_apivfs("cgroup2", mount_entry_path(m), "/sys/fs/cgroup", /* opts= */ NULL, p->runtime_scope); } +/* ANCIENT-MOD: before kernel 5.8 procfs options are per pid namespace (shared with the host /proc) and + * textual hidepid= / subset= do not exist, hence ignore ProtectProc=/ProcSubset= there. */ +static bool procfs_per_instance_options_supported(void) { + static int cached = -1; + struct utsname uts; + + if (cached >= 0) + return cached; + + if (uname(&uts) < 0) + return (cached = true); + + cached = strverscmp_improved(uts.release, "5.8") >= 0; + if (!cached) + log_debug("Kernel %s does not support per-instance procfs options, ignoring ProtectProc=/ProcSubset=.", uts.release); + + return cached; +} + static int mount_procfs(const MountEntry *m, const NamespaceParameters *p) { _cleanup_free_ char *opts = NULL; int r; @@ -1569,8 +1589,9 @@ static int mount_procfs(const MountEntry *m, const NamespaceParameters *p) { assert(m); assert(p); - if (p->protect_proc != PROTECT_PROC_DEFAULT || - p->proc_subset != PROC_SUBSET_ALL) { + if ((p->protect_proc != PROTECT_PROC_DEFAULT || + p->proc_subset != PROC_SUBSET_ALL) && + procfs_per_instance_options_supported()) { opts = strjoin("hidepid=", p->protect_proc == PROTECT_PROC_DEFAULT ? "off" : protect_proc_to_string(p->protect_proc)); @@ -2663,7 +2684,9 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) { * as well now. */ .propagation = MS_SLAVE, }); - if (_root_mount_fd < 0 && !ERRNO_IS_NEG_PRIVILEGE(_root_mount_fd) && _root_mount_fd != -EINVAL) + /* ANCIENT-MOD: also tolerate missing open_tree() (kernel < 5.2) */ + if (_root_mount_fd < 0 && !ERRNO_IS_NEG_PRIVILEGE(_root_mount_fd) && + !ERRNO_IS_NEG_NOT_SUPPORTED(_root_mount_fd) && _root_mount_fd != -EINVAL) return log_debug_errno(_root_mount_fd, "Failed to clone specified directory: %m"); root_mount_fd = _root_mount_fd; @@ -2866,7 +2889,8 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) { /* In case /proc is successfully mounted with pid tree subset only (ProcSubset=pid), the protective mounts to non-pid /proc paths would fail. But the pid only option may have failed gracefully, so let's try the mounts but it's not fatal if they don't succeed. */ - bool ignore_protect_proc = p->ignore_protect_paths || p->proc_subset == PROC_SUBSET_PID; + bool ignore_protect_proc = p->ignore_protect_paths || + (p->proc_subset == PROC_SUBSET_PID && procfs_per_instance_options_supported()); if (p->protect_kernel_tunables) { r = append_static_mounts(&ml, protect_kernel_tunables_proc_table, diff --git a/src/core/unit.c b/src/core/unit.c index 805642f2d3..1e7863e185 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -6654,7 +6654,7 @@ int unit_freezer_action(Unit *u, FreezerAction action) { assert(u); assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); - if (!unit_can_freeze(u)) + if (!cg_freezer_supported() || !unit_can_freeze(u)) return -EOPNOTSUPP; if (u->job) diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 1973e13b5b..87ab9fdc60 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -51,9 +52,20 @@ static bool EVENT_SOURCE_WATCH_PIDFD(const sd_event_source *s) { /* Returns true if this is a PID event source and can be implemented by watching EPOLLIN */ return s && s->type == SOURCE_CHILD && + s->child.pidfd >= 0 && (s->child.options & ~WNOWAIT) == WEXITED; } +/* ANCIENT-MOD: child sources without pidfd (kernel < 5.3) fall back to waitid(P_PID) */ +static int child_waitid(sd_event_source *s, siginfo_t *si, int options) { + assert(s); + + if (s->child.pidfd >= 0) + return waitid(P_PIDFD, s->child.pidfd, si, options); + + return waitid(P_PID, s->child.pid, si, options); +} + static bool event_source_is_online(sd_event_source *s) { assert(s); return s->enabled != SD_EVENT_OFF && !s->ratelimited; @@ -1099,10 +1111,12 @@ static sd_event_source* source_free(sd_event_source *s) { if (s->child.process_owned) { assert(s->child.pid > 0); - assert(s->child.pidfd >= 0); if (!s->child.exited) { - r = RET_NERRNO(pidfd_send_signal(s->child.pidfd, SIGKILL, NULL, 0)); + if (s->child.pidfd >= 0) + r = RET_NERRNO(pidfd_send_signal(s->child.pidfd, SIGKILL, NULL, 0)); + else + r = RET_NERRNO(kill(s->child.pid, SIGKILL)); if (r < 0 && r != -ESRCH) log_debug_errno(r, "Failed to kill process " PID_FMT ", ignoring: %m", s->child.pid); @@ -1112,7 +1126,7 @@ static sd_event_source* source_free(sd_event_source *s) { siginfo_t si = {}; /* Reap the child if we can */ - (void) waitid(P_PIDFD, s->child.pidfd, &si, WEXITED); + (void) child_waitid(s, &si, WEXITED); } } @@ -1649,10 +1663,22 @@ _public_ int sd_event_add_child( * PID, and make regular waitid() handling race-free. */ s->child.pidfd = pidfd_open(pid, 0); - if (s->child.pidfd < 0) - return -errno; + if (s->child.pidfd < 0) { + if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) + return -errno; - s->child.pidfd_owned = true; /* If we allocate the pidfd we own it by default */ + /* Without pidfd we depend on SIGCHLD via signalfd, which only works if it is blocked */ + r = signal_is_blocked(SIGCHLD); + if (r < 0) + return r; + if (r == 0) { + log_debug("pidfd_open() unavailable, blocking SIGCHLD to watch child processes."); + r = sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD); + if (r < 0) + return r; + } + } else + s->child.pidfd_owned = true; /* If we allocate the pidfd we own it by default */ s->wakeup = WAKEUP_EVENT_SOURCE; s->child.options = options; @@ -3257,7 +3283,6 @@ _public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, cons assert_return(s->type == SOURCE_CHILD, -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); assert_return(SIGNAL_VALID(sig), -EINVAL); - assert(s->child.pidfd >= 0); /* If we already have seen indication the process exited refuse sending a signal early. */ if (s->child.exited) @@ -3269,14 +3294,20 @@ _public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, cons if (si) copy = *si; - return RET_NERRNO(pidfd_send_signal(s->child.pidfd, sig, si ? © : NULL, flags)); + if (s->child.pidfd >= 0) + return RET_NERRNO(pidfd_send_signal(s->child.pidfd, sig, si ? © : NULL, flags)); + + if (flags != 0) + return -EOPNOTSUPP; + if (si) + return RET_NERRNO(syscall(SYS_rt_sigqueueinfo, s->child.pid, sig, ©)); + return RET_NERRNO(kill(s->child.pid, sig)); } _public_ int sd_event_source_get_child_pidfd_own(sd_event_source *s) { assert_return(s, -EINVAL); assert_return(s->type == SOURCE_CHILD, -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - assert(s->child.pidfd >= 0); return s->child.pidfd_owned; } @@ -3285,7 +3316,9 @@ _public_ int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own) { assert_return(s, -EINVAL); assert_return(s->type == SOURCE_CHILD, -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - assert(s->child.pidfd >= 0); + + if (s->child.pidfd < 0) + return -EOPNOTSUPP; s->child.pidfd_owned = own; return 0; @@ -3757,7 +3790,6 @@ static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priori HASHMAP_FOREACH(s, e->child_sources) { assert(s->type == SOURCE_CHILD); - assert(s->child.pidfd >= 0); if (s->priority > threshold) continue; @@ -3777,7 +3809,7 @@ static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priori continue; zero(s->child.siginfo); - if (waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, + if (child_waitid(s, &s->child.siginfo, WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | (s->child.options & ~WNOWAIT)) < 0) return negative_errno(); @@ -3791,7 +3823,7 @@ static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priori * from the queue, since there's no benefit in leaving it queued. */ assert(s->child.options & (WSTOPPED|WCONTINUED)); - (void) waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED))); + (void) child_waitid(s, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED))); } r = source_set_pending(s, true); @@ -4274,7 +4306,7 @@ static int source_dispatch(sd_event_source *s) { /* Now, reap the PID for good (unless WNOWAIT was specified by the caller). */ if (zombie) { - (void) waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG|WEXITED|(s->child.options & WNOWAIT)); + (void) child_waitid(s, &s->child.siginfo, WNOHANG|WEXITED|(s->child.options & WNOWAIT)); if (!FLAGS_SET(s->child.options, WNOWAIT)) s->child.waited = true; } diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c index 4c1baafcb6..3cb5820ff6 100644 --- a/src/libsystemd/sd-netlink/netlink-util.c +++ b/src/libsystemd/sd-netlink/netlink-util.c @@ -117,7 +117,8 @@ int rtnl_resolve_ifname_full( r = sd_netlink_call(*rtnl, message, 0, &reply); if (r >= 0) return parse_newlink_message(reply, ret_name, ret_altnames); - if (r != -ENODEV) + /* The kernels older than 76c9ac0ee878f6693d398d3a95ccaf85e1f597a6 (v5.5) return -EINVAL. */ + if (!IN_SET(r, -ENODEV, -EINVAL)) return r; } diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 6b5ee9afba..43aca316aa 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -858,7 +858,7 @@ static int create_session_message( if (!avoid_pidfd) { pidfd = pidfd_open(getpid_cached(), 0); - if (pidfd < 0) + if (pidfd < 0 && !ERRNO_IS_NOT_SUPPORTED(errno)) /* ANCIENT-MOD: kernel < 5.3 */ return -errno; } diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 64d1f1f6fe..1d6413f1f0 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2448,6 +2448,38 @@ static int link_update_driver(Link *link, sd_netlink_message *message) { return 1; /* needs reconfigure */ } +static int link_update_permanent_hardware_address_from_ethtool(Link *link, sd_netlink_message *message) { + int r; + + assert(link); + assert(link->manager); + assert(message); + + if (link->ethtool_permanent_hw_addr_read) + return 0; + + /* When udevd is running, read the permanent hardware address after the interface is + * initialized by udevd. Otherwise, ethtool may not work correctly. See issue #22538. + * When udevd is not running, read the value when the interface is detected. */ + if (udev_available() && !link->dev) + return 0; + + /* If the interface does not have a hardware address, then it will not have a permanent address either. */ + r = netlink_message_read_hw_addr(message, IFLA_ADDRESS, NULL); + if (r == -ENODATA) + return 0; + if (r < 0) + return log_link_debug_errno(link, r, "Failed to read IFLA_ADDRESS attribute: %m"); + + link->ethtool_permanent_hw_addr_read = true; + + r = ethtool_get_permanent_hw_addr(&link->manager->ethtool_fd, link->ifname, &link->permanent_hw_addr); + if (r < 0) + log_link_debug_errno(link, r, "Permanent hardware address not found, continuing without: %m"); + + return 0; +} + static int link_update_permanent_hardware_address(Link *link, sd_netlink_message *message) { int r; @@ -2459,10 +2491,15 @@ static int link_update_permanent_hardware_address(Link *link, sd_netlink_message return 0; r = netlink_message_read_hw_addr(message, IFLA_PERM_ADDRESS, &link->permanent_hw_addr); - if (r == -ENODATA) - return 0; - if (r < 0) - return log_link_debug_errno(link, r, "Failed to read IFLA_PERM_ADDRESS attribute: %m"); + if (r < 0) { + if (r != -ENODATA) + return log_link_debug_errno(link, r, "Failed to read IFLA_PERM_ADDRESS attribute: %m"); + + /* Fallback to ethtool for kernels older than v5.6 (f74877a5457d34d604dba6dbbb13c4c05bac8b93). */ + r = link_update_permanent_hardware_address_from_ethtool(link, message); + if (r < 0) + return r; + } if (link->permanent_hw_addr.length > 0) log_link_debug(link, "Saved permanent hardware address: %s", HW_ADDR_TO_STR(&link->permanent_hw_addr)); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 291589800f..b215c5125f 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -52,6 +52,7 @@ #include "networkd-wwan-bus.h" #include "ordered-set.h" #include "qdisc.h" +#include "selinux-util.h" #include "set.h" #include "stat-util.h" #include "string-util.h" @@ -1149,8 +1150,12 @@ int manager_enumerate(Manager *m) { if (r < 0) return log_error_errno(r, "Could not enumerate neighbors: %m"); + /* ANCIENT-MOD: NextHop support is added in kernel v5.3 (65ee00a9409f751188a8cdc0988167858eb4a536), + * and older kernels return -EOPNOTSUPP, or -EINVAL if SELinux is enabled. */ r = manager_enumerate_nexthop(m); - if (r < 0) + if (r == -EOPNOTSUPP || (r == -EINVAL && mac_selinux_enforcing())) + log_debug_errno(r, "Could not enumerate nexthops, ignoring: %m"); + else if (r < 0) return log_error_errno(r, "Could not enumerate nexthops: %m"); r = manager_enumerate_routes(m); diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c index 239ccd4e8f..ff96b73b35 100644 --- a/src/shared/firewall-util.c +++ b/src/shared/firewall-util.c @@ -1066,6 +1066,7 @@ static int fw_nftables_add_local_dnat_internal( sd_netlink_message *messages[3] = {}; CLEANUP_ELEMENTS(messages, sd_netlink_message_unref_array_clear); + static bool ipv6_supported = true; uint32_t data[5], key[2], dlen; size_t msgcnt = 0; int r; @@ -1074,6 +1075,9 @@ static int fw_nftables_add_local_dnat_internal( assert(add || !previous_remote); assert(IN_SET(af, AF_INET, AF_INET6)); + if (!ipv6_supported && af == AF_INET6) + return -EOPNOTSUPP; + if (!IN_SET(protocol, IPPROTO_TCP, IPPROTO_UDP)) return -EPROTONOSUPPORT; @@ -1127,6 +1131,14 @@ static int fw_nftables_add_local_dnat_internal( assert(msgcnt < ELEMENTSOF(messages)); r = sd_nfnl_call_batch(nfnl, messages, msgcnt, NFNL_DEFAULT_TIMEOUT_USECS); + if (r == -EOVERFLOW && af == AF_INET6) { + /* ANCIENT-MOD: The current implementation of DNAT in systemd requires kernel's + * fdb9c405e35bdc6e305b9b4e20ebc141ed14fc81 (v5.8), and the older kernel returns + * -EOVERFLOW. Let's treat the error as -EOPNOTSUPP. */ + log_debug_errno(r, "The current implementation of IPv6 DNAT in systemd requires kernel 5.8 or newer, ignoring: %m"); + ipv6_supported = false; + return -EOPNOTSUPP; + } if (r < 0) return r; diff --git a/src/shared/killall.c b/src/shared/killall.c index d81f385634..729e0e2480 100644 --- a/src/shared/killall.c +++ b/src/shared/killall.c @@ -64,6 +64,9 @@ static bool is_in_survivor_cgroup(const PidRef *pid) { } r = cg_get_xattr_bool(cgroup_path, "user.survive_final_kill_signal"); + /* user xattr support was added to kernel v5.7, try with the trusted namespace as a fallback */ + if (ERRNO_IS_NEG_XATTR_ABSENT(r)) + r = cg_get_xattr_bool(cgroup_path, "trusted.survive_final_kill_signal"); if (r < 0 && !ERRNO_IS_NEG_XATTR_ABSENT(r)) log_debug_errno(r, "Failed to get survive_final_kill_signal xattr of %s, ignoring: %m", diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 4e2b2f70c1..6b80b23103 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -5,6 +5,7 @@ #endif #include +#include #include #include #include @@ -200,6 +201,21 @@ static int loop_configure_fallback(int fd, const struct loop_config *c) { random_u64_range(UINT64_C(240) * USEC_PER_MSEC * n_attempts/64)); } + /* Work around a kernel bug, where changing offset/size of the loopback device doesn't correctly + * invalidate the buffer cache. For details see: + * + * https://android.googlesource.com/platform/system/apex/+/bef74542fbbb4cd629793f4efee8e0053b360570 + * + * This was fixed in kernel 5.0, see: + * + * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5db470e229e22b7eda6e23b5566e532c96fb5bc3 + * + * We'll run the work-around here in the legacy LOOP_SET_STATUS64 codepath. In the LOOP_CONFIGURE + * codepath above it should not be necessary. */ + if (c->info.lo_offset != 0 || c->info.lo_sizelimit != 0) + if (ioctl(fd, BLKFLSBUF, 0) < 0) + log_debug_errno(errno, "Failed to issue BLKFLSBUF ioctl, ignoring: %m"); + /* If a block size is requested then try to configure it. If that doesn't work, ignore errors, but * afterwards, let's validate what is in effect, and if it doesn't match what we want, fail */ if (c->block_size != 0) { diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c index f402990cf7..b40ff3fbd0 100644 --- a/src/shared/mount-setup.c +++ b/src/shared/mount-setup.c @@ -43,26 +43,35 @@ typedef struct MountPoint { } MountPoint; static int cgroupfs_mount_options(int priority, const char *type, char **ret) { + _cleanup_free_ char *opts = NULL; int r; assert(type); assert(streq(type, "cgroup2")); assert(ret); - /* memory_hugetlb_accounting mount option is since kernel v6.7 (8cba9576df601c384abd334a503c3f6e1e29eefb). */ + /* ANCIENT-MOD: memory_recursiveprot is probed (kernel >= 5.7) instead of being always passed. + * memory_hugetlb_accounting mount option is since kernel v6.7 (8cba9576df601c384abd334a503c3f6e1e29eefb). */ - r = mount_option_supported("cgroup2", "memory_hugetlb_accounting", /* value= */ NULL); - if (r <= 0) { - if (r < 0) - log_full_errno(priority, r, "Failed to determine whether cgroupfs supports 'memory_hugetlb_accounting' mount option, assuming not: %m"); - else - log_debug("'memory_hugetlb_accounting' not supported by cgroupfs, not using mount option."); - - *ret = NULL; - return 0; + FOREACH_STRING(o, "memory_recursiveprot", "memory_hugetlb_accounting") { + r = mount_option_supported("cgroup2", o, /* value= */ NULL); + if (r == -EAGAIN) + /* No fsopen(), i.e. kernel < 5.2, hence none of these options exist either */ + log_debug("Cannot probe cgroupfs mount option '%s', kernel too old, not using it.", o); + else if (r < 0 && streq(o, "memory_recursiveprot")) { + log_full_errno(priority, r, "Failed to determine whether cgroupfs supports '%s' mount option, assuming it does: %m", o); + if (!strextend_with_separator(&opts, ",", o)) + return log_oom_full(priority); + } else if (r < 0) + log_full_errno(priority, r, "Failed to determine whether cgroupfs supports '%s' mount option, assuming not: %m", o); + else if (r == 0) + log_debug("'%s' not supported by cgroupfs, not using mount option.", o); + else if (!strextend_with_separator(&opts, ",", o)) + return log_oom_full(priority); } - return strdup_to(ret, "memory_hugetlb_accounting"); + *ret = TAKE_PTR(opts); + return 0; } int mount_cgroupfs(const char *path) { @@ -82,7 +91,7 @@ int mount_cgroupfs(const char *path) { return r; /* These options shall be kept in sync with those in mount_table below. */ - if (!strprepend_with_separator(&opts, ",", "nsdelegate,memory_recursiveprot")) + if (!strprepend_with_separator(&opts, ",", "nsdelegate")) return log_oom(); return mount_nofollow_verbose(LOG_ERR, "cgroup2", path, "cgroup2", MS_NOSUID|MS_NOEXEC|MS_NODEV, opts); @@ -200,7 +209,7 @@ static const MountPoint mount_table[] = { .what = "cgroup2", .where = "/sys/fs/cgroup", .type = "cgroup2", - .options = "nsdelegate,memory_recursiveprot", + .options = "nsdelegate", .options_fn = cgroupfs_mount_options, .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV, .mode = MNT_FATAL|MNT_IN_CONTAINER|MNT_CHECK_WRITABLE, diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index 338bbc29d0..726dde8a53 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -343,13 +343,19 @@ TEST(close_all_fds) { _exit(EXIT_SUCCESS); } - r = ASSERT_OK(pidref_safe_fork("(caf-nomalloc)", flags, NULL)); + ASSERT_OK(r = pidref_safe_fork("(caf-nomalloc)", flags, NULL)); if (r == 0) { test_close_all_fds_inner(close_all_fds_without_malloc); _exit(EXIT_SUCCESS); } - r = ASSERT_OK(pidref_safe_fork("(caf-frugal)", flags, NULL)); + ASSERT_OK(r = pidref_safe_fork("(caf-proc)", flags, NULL)); + if (r == 0) { + test_close_all_fds_inner(close_all_fds_by_proc); + _exit(EXIT_SUCCESS); + } + + ASSERT_OK(r = pidref_safe_fork("(caf-frugal)", flags, NULL)); if (r == 0) { test_close_all_fds_inner(close_all_fds_frugal); _exit(EXIT_SUCCESS);