diff options
author | Lennart Poettering <lennart@poettering.net> | 2022-09-11 12:23:23 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2022-09-23 09:21:34 +0200 |
commit | cc43328c7f603fe25478aa8d8a7848b175c6941b (patch) | |
tree | 4ea481142bbf001c4d96e8cd378236a86e0fc7c1 /src | |
parent | a9bc518c08d4d3a111b637e348f5a60e5a50025f (diff) | |
download | systemd-cc43328c7f603fe25478aa8d8a7848b175c6941b.tar.gz |
tmpfiles: allow prefixing uid/gid/mode with ":" to only apply on creation
In some cases it is useful to specify the access mode/uid/gid for inodes
we create without also enforcing them on existing inodes. Let's add a
new flag for that: if the uid/gid/mode specificaitons are prefixed with
":", then they only apply to creation, not otherwise.
This is specifically useful for provisioning SSH keys later. Those we'd
like to provision like this:
<snip>
d /root :0700 root root -
d /root/.ssh :0700 root root -
f^ /root/.ssh/authorized_keys - - - - ssh.authorized_keys
</snip>
While /root/ + /root/.ssh/ being owned by root is pretty uncontroversial
the access mode of /root/ and /root/.ssh/ might not be. Hence we should
only have a default mode defined that is used when we create the dir,
but not otherwise.
Diffstat (limited to 'src')
-rw-r--r-- | src/tmpfiles/tmpfiles.c | 93 |
1 files changed, 56 insertions, 37 deletions
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index b29614d9a5..a168f95c7c 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -149,6 +149,9 @@ typedef struct Item { bool uid_set:1; bool gid_set:1; bool mode_set:1; + bool uid_only_create:1; + bool gid_only_create:1; + bool mode_only_create:1; bool age_set:1; bool mask_perms:1; bool attribute_set:1; @@ -867,9 +870,11 @@ static int fd_set_perms( const struct stat *st, CreationMode creation) { + bool do_chown, do_chmod; struct stat stbuf; mode_t new_mode; - bool do_chown; + uid_t new_uid; + gid_t new_gid; int r; assert(i); @@ -889,19 +894,20 @@ static int fd_set_perms( return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Refusing to set permissions on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.", path); + new_uid = i->uid_set && (creation != CREATION_EXISTING || !i->uid_only_create) ? i->uid : st->st_uid; + new_gid = i->gid_set && (creation != CREATION_EXISTING || !i->gid_only_create) ? i->gid : st->st_gid; /* Do we need a chown()? */ - do_chown = - (i->uid_set && i->uid != st->st_uid) || - (i->gid_set && i->gid != st->st_gid); + do_chown = (new_uid != st->st_uid) || (new_gid != st->st_gid); /* Calculate the mode to apply */ - new_mode = i->mode_set ? (i->mask_perms ? - process_mask_perms(i->mode, st->st_mode) : - i->mode) : - (st->st_mode & 07777); + new_mode = i->mode_set && (creation != CREATION_EXISTING || !i->mode_only_create) ? + (i->mask_perms ? process_mask_perms(i->mode, st->st_mode) : i->mode) : + (st->st_mode & 07777); - if (i->mode_set && do_chown) { + do_chmod = ((new_mode ^ st->st_mode) & 07777) != 0; + + if (do_chmod && do_chown) { /* Before we issue the chmod() let's reduce the access mode to the common bits of the old and * the new mode. That way there's no time window where the file exists under the old owner * with more than the old access modes — and not under the new owner with more than the new @@ -924,35 +930,25 @@ static int fd_set_perms( } if (do_chown) { - log_debug("Changing \"%s\" to owner "UID_FMT":"GID_FMT, - path, - i->uid_set ? i->uid : UID_INVALID, - i->gid_set ? i->gid : GID_INVALID); - - if (fchownat(fd, - "", - i->uid_set ? i->uid : UID_INVALID, - i->gid_set ? i->gid : GID_INVALID, + log_debug("Changing \"%s\" to owner "UID_FMT":"GID_FMT, path, new_uid, new_gid); + + if (fchownat(fd, "", + new_uid != st->st_uid ? new_uid : UID_INVALID, + new_gid != st->st_gid ? new_gid : GID_INVALID, AT_EMPTY_PATH) < 0) return log_error_errno(errno, "fchownat() of %s failed: %m", path); } /* Now, apply the final mode. We do this in two cases: when the user set a mode explicitly, or after a * chown(), since chown()'s mangle the access mode in regards to sgid/suid in some conditions. */ - if (i->mode_set || do_chown) { + if (do_chmod || do_chown) { if (S_ISLNK(st->st_mode)) log_debug("Skipping mode fix for symlink %s.", path); else { - /* Check if the chmod() is unnecessary. Note that if we did a chown() before we always - * chmod() here again, since it might have mangled the bits. */ - if (!do_chown && ((new_mode ^ st->st_mode) & 07777) == 0) - log_debug("\"%s\" matches mode %o already.", path, new_mode); - else { - log_debug("Changing \"%s\" to mode %o.", path, new_mode); - r = fchmod_opath(fd, new_mode); - if (r < 0) - return log_error_errno(r, "fchmod() of %s failed: %m", path); - } + log_debug("Changing \"%s\" to mode %o.", path, new_mode); + r = fchmod_opath(fd, new_mode); + if (r < 0) + return log_error_errno(r, "fchmod() of %s failed: %m", path); } } @@ -2772,12 +2768,15 @@ static bool item_compatible(const Item *a, const Item *b) { a->uid_set == b->uid_set && a->uid == b->uid && + a->uid_only_create == b->uid_only_create && a->gid_set == b->gid_set && a->gid == b->gid && + a->gid_only_create == b->gid_only_create && a->mode_set == b->mode_set && a->mode == b->mode && + a->mode_only_create == b->mode_only_create && a->age_set == b->age_set && a->age == b->age && @@ -3365,32 +3364,52 @@ static int parse_line( } if (!empty_or_dash(user)) { - r = find_uid(user, &i.uid, uid_cache); + const char *u; + + u = startswith(user, ":"); + if (u) + i.uid_only_create = true; + else + u = user; + + r = find_uid(u, &i.uid, uid_cache); if (r < 0) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve user '%s': %m", user); + return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve user '%s': %m", u); } i.uid_set = true; } if (!empty_or_dash(group)) { - r = find_gid(group, &i.gid, gid_cache); + const char *g; + + g = startswith(group, ":"); + if (g) + i.gid_only_create = true; + else + g = group; + + r = find_gid(g, &i.gid, gid_cache); if (r < 0) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve group '%s'.", group); + return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve group '%s'.", g); } i.gid_set = true; } if (!empty_or_dash(mode)) { - const char *mm = mode; + const char *mm; unsigned m; - if (*mm == '~') { - i.mask_perms = true; - mm++; + for (mm = mode;; mm++) { + if (*mm == '~') + i.mask_perms = true; + else if (*mm == ':') + i.mode_only_create = true; + else + break; } r = parse_mode(mm, &m); |