summaryrefslogtreecommitdiff
path: root/src/tmpfiles
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2022-09-11 12:23:23 +0200
committerLennart Poettering <lennart@poettering.net>2022-09-23 09:21:34 +0200
commitcc43328c7f603fe25478aa8d8a7848b175c6941b (patch)
tree4ea481142bbf001c4d96e8cd378236a86e0fc7c1 /src/tmpfiles
parenta9bc518c08d4d3a111b637e348f5a60e5a50025f (diff)
downloadsystemd-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/tmpfiles')
-rw-r--r--src/tmpfiles/tmpfiles.c93
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);