diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index ebc08560fe9e8..23023740901ac 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -344,6 +344,15 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svn*BenQ*:pn*Joybook*R22*:* # Clevo ########################################################### +# Clevo PA70ES (Avell C73) +# The ITE keyboard controller firmware (version 0xAB83) is shared with +# the X+ piccolo. The piccolo rule (below) matches by input device ID +# and remaps KP_Enter to Enter since the piccolo has no numpad and its +# main Enter sends the wrong scan code. The PA70ES has a real numpad, +# so the remap breaks KP_Enter. This restores the correct mapping. +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnNotebook:pnPA70ES:* + KEYBOARD_KEY_9c=kpenter + evdev:atkbd:dmi:bvn*:bvr*:bd*:svnNotebook:pnW65_67SZ:* KEYBOARD_KEY_a0=!mute KEYBOARD_KEY_a2=!playpause diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index 8a908c412519f..e5f694611a60d 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -76,6 +76,10 @@ a /path-or-glob/to/set/acls - - - - POSIX a+ /path-or-glob/to/append/acls - - - - POSIX ACLs A /path-or-glob/to/set/acls/recursively - - - - POSIX ACLs A+ /path-or-glob/to/append/acls/recursively - - - - POSIX ACLs +k /path-or-glob/to/set/caps - - - - file capabilities +k+ /path-or-glob/to/adjust/caps - - - - file capabilities +K /path-or-glob/to/set/caps/recursively - - - - file capabilities +K+ /path-or-glob/to/adjust/caps/recursively - - - - file capabilities @@ -484,6 +488,37 @@ L /tmp/foobar - - - - /dev/null + + + k + k+ + Set file capabilities, see capabilities7. + Lines of this type accept shell-style globs in place of normal path names. Does not follow + symlinks. + + The syntax follows cap_text_formats7. It + also supports rootuid=INT for the user namespace root + user ID. + + If suffixed with +, current capabilities on the file that are not touched by the expression + will be kept. For example, if all cap_setuid capabilities need to be removed but + others should be kept, one can use k+ with cap_setuid= or + cap_setuid-eip. + + + + + + K + K+ + Same as k and + k+, but recursive. Does not follow + symlinks. + + + @@ -565,8 +600,8 @@ w- /proc/sys/vm/swappiness - - - - 10 -, the default is used: 0755 for directories, 0644 for all other file objects. For z, Z lines, if omitted or when set to -, the file access mode will not be modified. This parameter is ignored for x, - r, R, L, t, and - a lines. + r, R, L, t, + a, and k lines. Optionally, if prefixed with ~, the access mode is masked based on the already set access bits for existing file or directories: if the existing file has all executable bits unset, @@ -707,7 +742,8 @@ d /tmp/foo/bar - - - bmA:1h - suffixed by a newline. For C, specifies the source file or directory. For t and T, determines extended attributes to be set. For a and A, determines ACL attributes to be set. For h and H, - determines the file attributes to set. Ignored for all other lines. + determines the file attributes to set. For k and K, determines + file capabilities to be set. Ignored for all other lines. This field can contain specifiers, see below. diff --git a/po/ro.po b/po/ro.po index 876dc27773b09..97dcf594331a3 100644 --- a/po/ro.po +++ b/po/ro.po @@ -4,21 +4,22 @@ # va511e , 2015. # Daniel Șerbănescu , 2015, 2017. # Vlad , 2020, 2021. +# Petru Rebeja , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2021-01-12 17:36+0000\n" -"Last-Translator: Vlad \n" +"PO-Revision-Date: 2026-05-09 05:59+0000\n" +"Last-Translator: Petru Rebeja \n" "Language-Team: Romanian \n" +"systemd/main/ro/>\n" "Language: ro\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < " "20)) ? 1 : 2;\n" -"X-Generator: Weblate 4.4\n" +"X-Generator: Weblate 5.17.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -125,16 +126,12 @@ msgstr "" "utilizator." #: src/home/org.freedesktop.home1.policy:53 -#, fuzzy msgid "Update your home area" -msgstr "Actualizează un spațiu personal" +msgstr "Actualizați-vă spațiu personal" #: src/home/org.freedesktop.home1.policy:54 -#, fuzzy msgid "Authentication is required to update your home area." -msgstr "" -"Autentificarea este necesară pentru a actualiza spațiul personal al unui " -"utilizator." +msgstr "Pentru a-ți actualiza spațiul personal, este necesară autentificarea." #: src/home/org.freedesktop.home1.policy:63 msgid "Resize a home area" @@ -158,16 +155,14 @@ msgstr "" "al unui utilizator." #: src/home/org.freedesktop.home1.policy:83 -#, fuzzy msgid "Activate a home area" -msgstr "Crează un spațiu personal" +msgstr "Activează un spațiu personal" #: src/home/org.freedesktop.home1.policy:84 -#, fuzzy msgid "Authentication is required to activate a user's home area." msgstr "" -"Autentificarea este necesară pentru a crea spațiul personal al unui " -"utilizator." +"Pentru a activa spațiul personal al unui utilizator este necesară " +"autentificarea." #: src/home/org.freedesktop.home1.policy:93 msgid "Manage Home Directory Signing Keys" diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 44843f3ca77ec..b6007f6207ed6 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -14,6 +14,7 @@ #include "bitfield.h" #include "btrfs-util.h" #include "build.h" +#include "capability-list.h" #include "capability-util.h" #include "chase.h" #include "chattr-util.h" @@ -104,6 +105,8 @@ typedef enum ItemType { RECURSIVE_SET_XATTR = 'T', SET_ACL = 'a', RECURSIVE_SET_ACL = 'A', + SET_FCAPS = 'k', + RECURSIVE_SET_FCAPS = 'K', SET_ATTRIBUTE = 'h', RECURSIVE_SET_ATTRIBUTE = 'H', IGNORE_PATH = 'x', @@ -126,6 +129,18 @@ typedef enum AgeBy { AGE_BY_DEFAULT_DIR = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_MTIME, } AgeBy; +typedef struct FCapsPatch { + uint64_t mask; + uint64_t set; +} FCapsPatch; + +typedef struct FCapsUpdate { + uid_t rootuid; + FCapsPatch inheritable; + FCapsPatch permitted; + FCapsPatch effective; +} FCapsUpdate; + typedef struct Item { ItemType type; @@ -139,6 +154,7 @@ typedef struct Item { acl_t acl_access_exec; acl_t acl_default; #endif + FCapsUpdate fcaps; uid_t uid; gid_t gid; mode_t mode; @@ -171,6 +187,8 @@ typedef struct Item { bool ignore_if_target_missing:1; + bool fcaps_set:1; + OperationMask done; } Item; @@ -408,6 +426,8 @@ static bool needs_glob(ItemType t) { RECURSIVE_SET_XATTR, SET_ACL, RECURSIVE_SET_ACL, + SET_FCAPS, + RECURSIVE_SET_FCAPS, SET_ATTRIBUTE, RECURSIVE_SET_ATTRIBUTE, IGNORE_PATH, @@ -1502,6 +1522,271 @@ static int path_set_acls( return r; } +static int capability_vfs_from_string(const char *s, FCapsUpdate *ret) { + FCapsUpdate set = { + .rootuid = UID_INVALID, + }; + + assert(s); + assert(ret); + + for (const char *p = s;;) { + _cleanup_free_ char *word = NULL, *keys = NULL; + char *value, sep; + int r; + + r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX); + if (r < 0) + return log_debug_errno(r, "Failed to split words from '%s': %m", p); + if (r == 0) + break; + + value = strpbrk(word, "=+-"); + if (!value) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse key/value '%s': %m", word); + keys = strndup(word, value - word); + if (!keys) + return log_oom(); + sep = *value; + value++; + + if (sep == '=' && streq(keys, "rootuid")) { + r = parse_uid(value, &set.rootuid); + if (r < 0) + return log_debug_errno(r, "Failed to parse rootuid value '%s': %m", value); + } else { + uint64_t caps = 0; + + if (!in_charset(value, "eip")) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse value '%s': %m", value); + + if (STR_IN_SET(keys, "all", "")) + caps = all_capabilities(); + else + for (const char *remaining_keys = keys; remaining_keys && *remaining_keys;) { + _cleanup_free_ char *key = NULL; + + r = extract_first_word(&remaining_keys, &key, ",", /* flags= */0); + if (r < 0) + return log_debug_errno(r, "Failed to parse capability list '%s': %m", keys); + if (r == 0) + break; + if (streq(key, "all")) + caps = all_capabilities(); + else { + r = capability_from_name(key); + if (r < 0) + return log_debug_errno(r, "Failed to parse capability '%s': %m", key); + caps |= UINT64_C(1) << r; + } + } + + if (sep == '=') { + set.permitted.mask |= caps; + set.inheritable.mask |= caps; + set.effective.mask |= caps; + } + if (IN_SET(sep, '=', '+')) { + if (strchr(value, 'p')) + set.permitted.set |= caps; + if (strchr(value, 'i')) + set.inheritable.set |= caps; + if (strchr(value, 'e')) + set.effective.set |= caps; + } else { + if (strchr(value, 'p')) { + set.permitted.mask |= caps; + set.permitted.set &= ~caps; + } + if (strchr(value, 'i')) { + set.inheritable.mask |= caps; + set.inheritable.set &= ~caps; + } + if (strchr(value, 'e')) { + set.effective.mask |= caps; + set.effective.set &= ~caps; + } + } + } + } + + *ret = set; + + return 0; +} + +static size_t cap_data_size(uint32_t revision) { + switch (revision) { + case VFS_CAP_REVISION_1: + return XATTR_CAPS_SZ_1; + case VFS_CAP_REVISION_2: + return XATTR_CAPS_SZ_2; + case VFS_CAP_REVISION_3: + return XATTR_CAPS_SZ_3; + default: + return SIZE_MAX; + } +} + +static bool inode_type_can_fcaps(mode_t mode) { + return S_ISREG(mode); +} + +static int apply_fcaps(int fd, const char *path, bool append, const FCapsUpdate *set) { + struct vfs_ns_cap_data val = { + .magic_etc = htole32(VFS_CAP_REVISION), + }; + le32_t effective[VFS_CAP_U32] = {}; + struct stat st; + int r; + + assert(fd >= 0); + assert(path); + assert(set); + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat(%s): %m", path); + + if (hardlink_vulnerable(&st)) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "Refusing to set file capabilities on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.", + path); + + if (!inode_type_can_fcaps(st.st_mode)) { + log_debug("Skipping file capabilities for '%s' (inode type does not support file capabilities).", path); + return 0; + } + + if (append) { + _cleanup_free_ char *xattr_data = NULL; + size_t xattr_data_len; + + r = fgetxattr_malloc(fd, "security.capability", &xattr_data, &xattr_data_len); + if (r == -ENODATA) + log_debug("No capabilities found for '%s'", path); + else if (r < 0) + return log_error_errno(r, "Failed to read capabilities of '%s': %m", path); + else { + _cleanup_free_ struct vfs_ns_cap_data *original = NULL; + + if (xattr_data_len < endoffsetof_field(struct vfs_ns_cap_data, magic_etc)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extended attributes for capabilities are too small"); + + original = realloc0(xattr_data, sizeof(struct vfs_ns_cap_data)); + if (!original) + return log_oom(); + xattr_data = NULL; + + size_t expected_size = cap_data_size(le32toh(original->magic_etc) & VFS_CAP_REVISION_MASK); + if (expected_size == SIZE_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown type of file capabilities"); + if (xattr_data_len != expected_size) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Size of file capabilities does not match its type"); + + if (FLAGS_SET(le32toh(original->magic_etc), VFS_CAP_FLAGS_EFFECTIVE)) + for (size_t n = 0; n < VFS_CAP_U32; n++) + effective[n] = original->data[n].permitted | original->data[n].inheritable; + + for (size_t n = 0; n < VFS_CAP_U32; n++) { + val.data[n].permitted = original->data[n].permitted; + val.data[n].inheritable = original->data[n].inheritable; + } + + val.rootid = original->rootid; + } + } + + for (size_t n = 0; n < VFS_CAP_U32; n++) { + size_t bit_shift = 32*n; + val.data[n].inheritable &= htole32(~(set->inheritable.mask >> bit_shift) & UINT32_C(0xffffffff)); + val.data[n].inheritable |= htole32((set->inheritable.set >> bit_shift) & UINT32_C(0xffffffff)); + val.data[n].permitted &= htole32(~(set->permitted.mask >> bit_shift) & UINT32_C(0xffffffff)); + val.data[n].permitted |= htole32((set->permitted.set >> bit_shift) & UINT32_C(0xffffffff)); + effective[n] &= htole32(~(set->effective.mask >> bit_shift) & UINT32_C(0xffffffff)); + effective[n] |= htole32((set->effective.set >> bit_shift) & UINT32_C(0xffffffff)); + if (effective[n] != 0) + val.magic_etc |= htole32(VFS_CAP_FLAGS_EFFECTIVE); + } + + if (FLAGS_SET(le32toh(val.magic_etc), VFS_CAP_FLAGS_EFFECTIVE)) + for (size_t n = 0; n < VFS_CAP_U32; n++) + if ((val.data[n].permitted | val.data[n].inheritable) != effective[n]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Inconsistent effective bits"); + + if (set->rootuid != UID_INVALID) + val.rootid = htole32(set->rootuid); + + log_action("Would try to set", "Trying to set", + "%s capabilities on %s", path); + + if (!arg_dry_run) { + r = xsetxattr_full(fd, /* path= */ NULL, AT_EMPTY_PATH, "security.capability", (void*)&val, sizeof(val), /* xattr_flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to setcap '%s': %m", path); + } + + return 0; +} + +static int parse_caps_from_arg(Item *item) { + FCapsUpdate fcaps; + int r; + + assert(item); + + r = capability_vfs_from_string(item->argument, &fcaps); + if (r < 0) { + log_full_errno(arg_graceful ? LOG_DEBUG : LOG_WARNING, + r, "Failed to parse capabilities \"%s\", ignoring: %m", item->argument); + return 0; + } + + item->fcaps_set = true; + item->fcaps = fcaps; + + return 0; +} + +static int fd_set_caps( + Context *c, + Item *item, + int fd, + const char *path, + const struct stat *st, + CreationMode creation) { + assert(c); + assert(item); + assert(fd >= 0); + assert(path); + + if (!item->fcaps_set) + return 0; + return apply_fcaps(fd, path, item->append_or_force, &item->fcaps); +} + +static int path_set_caps( + Context *c, + Item *item, + const char *path, + CreationMode creation) { + _cleanup_close_ int fd = -EBADF; + + assert(c); + assert(item); + assert(path); + + if (!item->fcaps_set) + return 0; + + fd = path_open_safe(path); + if (fd == -ENOENT) + return 0; + if (fd < 0) + return fd; + + return apply_fcaps(fd, path, item->append_or_force, &item->fcaps); +} + static int parse_attribute_from_arg(Item *item) { static const struct { char character; @@ -2955,6 +3240,18 @@ static int create_item(Context *c, Item *i) { return r; break; + case SET_FCAPS: + r = glob_item(c, i, path_set_caps); + if (r < 0) + return r; + break; + + case RECURSIVE_SET_FCAPS: + r = glob_item_recursively(c, i, fd_set_caps); + if (r < 0) + return r; + break; + case SET_ATTRIBUTE: r = glob_item(c, i, path_set_attribute); if (r < 0) @@ -3816,6 +4113,23 @@ static int parse_line( return r; break; + case SET_FCAPS: + case RECURSIVE_SET_FCAPS: + if (unbase64) { + *invalid_config = true; + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for capabilities."); + } + if (!i.argument) { + *invalid_config = true; + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Set capabilities requires argument."); + } + r = parse_caps_from_arg(&i); + if (r < 0) + return r; + break; + case SET_ATTRIBUTE: case RECURSIVE_SET_ATTRIBUTE: if (unbase64) { diff --git a/test/units/TEST-22-TMPFILES.22.sh b/test/units/TEST-22-TMPFILES.22.sh new file mode 100755 index 0000000000000..37c9f709b75ce --- /dev/null +++ b/test/units/TEST-22-TMPFILES.22.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +rm -f /tmp/setcap +touch /tmp/setcap + +systemd-tmpfiles --dry-run --create - <