summaryrefslogtreecommitdiff
path: root/src/tmpfiles
diff options
context:
space:
mode:
authorMike Yuan <me@yhndnzj.com>2022-12-17 00:44:06 +0800
committerMike Yuan <me@yhndnzj.com>2023-04-27 15:15:09 +0800
commit26d98cdd78cb5283f5771bd5866997acc494b067 (patch)
tree6b59215bfcd3ae90e8c54915916eff3c08b80b8d /src/tmpfiles
parent49c778e6bf70ebf230989ab84e9ce7f1b26beef2 (diff)
downloadsystemd-26d98cdd78cb5283f5771bd5866997acc494b067.tar.gz
tmpfiles: add conditionalized execute bit (X) support
According to setfacl(1), "the character X stands for the execute permission if the file is a directory or already has execute permission for some user." After this commit, parse_acl() would return 3 acl objects. The newly-added acl_exec object contains entries that are subject to conditionalized execute bit mangling. In tmpfiles, we would iterate the acl_exec object, check the permission of the target files, and remove the execute bit if necessary. Here's an example entry: A /tmp/test - - - - u:test:rwX Closes #25114
Diffstat (limited to 'src/tmpfiles')
-rw-r--r--src/tmpfiles/tmpfiles.c154
1 files changed, 149 insertions, 5 deletions
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index 2eb6e5ea33..de72df2908 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -138,6 +138,7 @@ typedef struct Item {
char **xattrs;
#if HAVE_ACL
acl_t acl_access;
+ acl_t acl_access_exec;
acl_t acl_default;
#endif
uid_t uid;
@@ -1127,17 +1128,145 @@ static int parse_acls_from_arg(Item *item) {
/* If append_or_force (= modify) is set, we will not modify the acl
* afterwards, so the mask can be added now if necessary. */
- r = parse_acl(item->argument, &item->acl_access, &item->acl_default, !item->append_or_force);
+ r = parse_acl(item->argument, &item->acl_access, &item->acl_access_exec,
+ &item->acl_default, !item->append_or_force);
if (r < 0)
- log_warning_errno(r, "Failed to parse ACL \"%s\": %m. Ignoring", item->argument);
+ log_warning_errno(r, "Failed to parse ACL \"%s\", ignoring: %m", item->argument);
#else
- log_warning("ACLs are not supported. Ignoring.");
+ log_warning("ACLs are not supported, ignoring.");
#endif
return 0;
}
#if HAVE_ACL
+static int parse_acl_cond_exec(
+ const char *path,
+ acl_t access, /* could be empty (NULL) */
+ acl_t cond_exec,
+ const struct stat *st,
+ bool append,
+ acl_t *ret) {
+
+ _cleanup_(acl_freep) acl_t parsed = NULL;
+ acl_entry_t entry;
+ acl_permset_t permset;
+ bool has_exec;
+ int r;
+
+ assert(path);
+ assert(ret);
+ assert(st);
+
+ parsed = access ? acl_dup(access) : acl_init(0);
+ if (!parsed)
+ return -errno;
+
+ /* Since we substitute 'X' with 'x' in parse_acl(), we just need to copy the entries over
+ * for directories */
+ if (S_ISDIR(st->st_mode)) {
+ for (r = acl_get_entry(cond_exec, ACL_FIRST_ENTRY, &entry);
+ r > 0;
+ r = acl_get_entry(cond_exec, ACL_NEXT_ENTRY, &entry)) {
+
+ acl_entry_t parsed_entry;
+
+ if (acl_create_entry(&parsed, &parsed_entry) < 0)
+ return -errno;
+
+ if (acl_copy_entry(parsed_entry, entry) < 0)
+ return -errno;
+ }
+ if (r < 0)
+ return -errno;
+
+ goto finish;
+ }
+
+ has_exec = st->st_mode & S_IXUSR;
+
+ if (!has_exec && append) {
+ _cleanup_(acl_freep) acl_t old = NULL;
+
+ old = acl_get_file(path, ACL_TYPE_ACCESS);
+ if (!old)
+ return -errno;
+
+ for (r = acl_get_entry(old, ACL_FIRST_ENTRY, &entry);
+ r > 0;
+ r = acl_get_entry(old, ACL_NEXT_ENTRY, &entry)) {
+
+ if (acl_get_permset(entry, &permset) < 0)
+ return -errno;
+
+ r = acl_get_perm(permset, ACL_EXECUTE);
+ if (r < 0)
+ return -errno;
+ if (r > 0) {
+ has_exec = true;
+ break;
+ }
+ }
+ if (r < 0)
+ return -errno;
+ }
+
+ /* Check if we're about to set the execute bit in acl_access */
+ if (!has_exec && access) {
+ for (r = acl_get_entry(access, ACL_FIRST_ENTRY, &entry);
+ r > 0;
+ r = acl_get_entry(access, ACL_NEXT_ENTRY, &entry)) {
+
+ if (acl_get_permset(entry, &permset) < 0)
+ return -errno;
+
+ r = acl_get_perm(permset, ACL_EXECUTE);
+ if (r < 0)
+ return -errno;
+ if (r > 0) {
+ has_exec = true;
+ break;
+ }
+ }
+ if (r < 0)
+ return -errno;
+ }
+
+ for (r = acl_get_entry(cond_exec, ACL_FIRST_ENTRY, &entry);
+ r > 0;
+ r = acl_get_entry(cond_exec, ACL_NEXT_ENTRY, &entry)) {
+
+ acl_entry_t parsed_entry;
+
+ if (acl_create_entry(&parsed, &parsed_entry) < 0)
+ return -errno;
+
+ if (acl_copy_entry(parsed_entry, entry) < 0)
+ return -errno;
+
+ if (!has_exec) {
+ if (acl_get_permset(parsed_entry, &permset) < 0)
+ return -errno;
+
+ if (acl_delete_perm(permset, ACL_EXECUTE) < 0)
+ return -errno;
+ }
+ }
+ if (r < 0)
+ return -errno;
+
+finish:
+ if (!append) { /* want_mask = true */
+ r = calc_acl_mask_if_needed(&parsed);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(parsed);
+
+ return 0;
+}
+
static int path_set_acl(
const char *path,
const char *pretty,
@@ -1202,6 +1331,7 @@ static int fd_set_acls(
int r = 0;
#if HAVE_ACL
+ _cleanup_(acl_freep) acl_t access_with_exec_parsed = NULL;
struct stat stbuf;
assert(item);
@@ -1224,7 +1354,18 @@ static int fd_set_acls(
return 0;
}
- if (item->acl_access)
+ if (item->acl_access_exec) {
+ r = parse_acl_cond_exec(FORMAT_PROC_FD_PATH(fd),
+ item->acl_access,
+ item->acl_access_exec,
+ st,
+ item->append_or_force,
+ &access_with_exec_parsed);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse conditionalized execute bit for \"%s\": %m", path);
+
+ r = path_set_acl(FORMAT_PROC_FD_PATH(fd), path, ACL_TYPE_ACCESS, access_with_exec_parsed, item->append_or_force);
+ } else if (item->acl_access)
r = path_set_acl(FORMAT_PROC_FD_PATH(fd), path, ACL_TYPE_ACCESS, item->acl_access, item->append_or_force);
/* set only default acls to folders */
@@ -1237,7 +1378,7 @@ static int fd_set_acls(
}
if (r > 0)
- return -r; /* already warned */
+ return -r; /* already warned in path_set_acl */
/* The above procfs paths don't work if /proc is not mounted. */
if (r == -ENOENT && proc_mounted() == 0)
@@ -2867,6 +3008,9 @@ static void item_free_contents(Item *i) {
if (i->acl_access)
acl_free(i->acl_access);
+ if (i->acl_access_exec)
+ acl_free(i->acl_access_exec);
+
if (i->acl_default)
acl_free(i->acl_default);
#endif