mirror of
https://github.com/systemd/systemd
synced 2024-10-02 22:37:25 +00:00
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
This commit is contained in:
parent
49c778e6bf
commit
26d98cdd78
|
@ -446,13 +446,15 @@ L /tmp/foobar - - - - /dev/null</programlisting>
|
|||
<term><varname>a+</varname></term>
|
||||
<listitem><para>Set POSIX ACLs (access control lists), see <citerefentry
|
||||
project='man-pages'><refentrytitle>acl</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry>. If suffixed with <varname>+</varname>, the specified
|
||||
entries will be added to the existing set. <command>systemd-tmpfiles</command> will automatically
|
||||
add the required base entries for user and group based on the access mode of the file, unless base
|
||||
entries already exist or are explicitly specified. The mask will be added if not specified
|
||||
explicitly or already present. Lines of this type accept shell-style globs in place of normal path
|
||||
names. This can be useful for allowing additional access to certain files. Does not follow
|
||||
symlinks.</para></listitem>
|
||||
<manvolnum>5</manvolnum></citerefentry>. Additionally, if 'X' is used, the execute bit is set only
|
||||
if the file is a directory or already has execute permission for some user, as mentioned in
|
||||
<citerefentry project='man-pages'><refentrytitle>setfacl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
|
||||
If suffixed with <varname>+</varname>, the specified entries will be added to the existing set.
|
||||
<command>systemd-tmpfiles</command> will automatically add the required base entries for user
|
||||
and group based on the access mode of the file, unless base entries already exist or are explicitly
|
||||
specified. The mask will be added if not specified explicitly or already present. Lines of this type
|
||||
accept shell-style globs in place of normal path names. This can be useful for allowing additional
|
||||
access to certain files. Does not follow symlinks.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
|
|
|
@ -209,14 +209,20 @@ int acl_search_groups(const char *path, char ***ret_groups) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
int parse_acl(const char *text, acl_t *ret_acl_access, acl_t *ret_acl_default, bool want_mask) {
|
||||
_cleanup_free_ char **a = NULL, **d = NULL; /* strings are not freed */
|
||||
_cleanup_strv_free_ char **split = NULL;
|
||||
int r = -EINVAL;
|
||||
_cleanup_(acl_freep) acl_t a_acl = NULL, d_acl = NULL;
|
||||
int parse_acl(
|
||||
const char *text,
|
||||
acl_t *ret_acl_access,
|
||||
acl_t *ret_acl_access_exec, /* extra rules to apply to inodes subject to uppercase X handling */
|
||||
acl_t *ret_acl_default,
|
||||
bool want_mask) {
|
||||
|
||||
_cleanup_strv_free_ char **a = NULL, **e = NULL, **d = NULL, **split = NULL;
|
||||
_cleanup_(acl_freep) acl_t a_acl = NULL, e_acl = NULL, d_acl = NULL;
|
||||
int r;
|
||||
|
||||
assert(text);
|
||||
assert(ret_acl_access);
|
||||
assert(ret_acl_access_exec);
|
||||
assert(ret_acl_default);
|
||||
|
||||
split = strv_split(text, ",");
|
||||
|
@ -224,13 +230,38 @@ int parse_acl(const char *text, acl_t *ret_acl_access, acl_t *ret_acl_default, b
|
|||
return -ENOMEM;
|
||||
|
||||
STRV_FOREACH(entry, split) {
|
||||
char *p;
|
||||
_cleanup_strv_free_ char **entry_split = NULL;
|
||||
_cleanup_free_ char *entry_join = NULL;
|
||||
int n;
|
||||
|
||||
p = STARTSWITH_SET(*entry, "default:", "d:");
|
||||
if (p)
|
||||
r = strv_push(&d, p);
|
||||
else
|
||||
r = strv_push(&a, *entry);
|
||||
n = strv_split_full(&entry_split, *entry, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
|
||||
if (n < 0)
|
||||
return n;
|
||||
|
||||
if (n < 3 || n > 4)
|
||||
return -EINVAL;
|
||||
|
||||
string_replace_char(entry_split[n-1], 'X', 'x');
|
||||
|
||||
if (n == 4) {
|
||||
if (!STR_IN_SET(entry_split[0], "default", "d"))
|
||||
return -EINVAL;
|
||||
|
||||
entry_join = strv_join(entry_split + 1, ":");
|
||||
if (!entry_join)
|
||||
return -ENOMEM;
|
||||
|
||||
r = strv_consume(&d, TAKE_PTR(entry_join));
|
||||
} else { /* n == 3 */
|
||||
entry_join = strv_join(entry_split, ":");
|
||||
if (!entry_join)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!streq(*entry, entry_join))
|
||||
r = strv_consume(&e, TAKE_PTR(entry_join));
|
||||
else
|
||||
r = strv_consume(&a, TAKE_PTR(entry_join));
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
@ -253,6 +284,20 @@ int parse_acl(const char *text, acl_t *ret_acl_access, acl_t *ret_acl_default, b
|
|||
}
|
||||
}
|
||||
|
||||
if (!strv_isempty(e)) {
|
||||
_cleanup_free_ char *join = NULL;
|
||||
|
||||
join = strv_join(e, ",");
|
||||
if (!join)
|
||||
return -ENOMEM;
|
||||
|
||||
e_acl = acl_from_text(join);
|
||||
if (!e_acl)
|
||||
return -errno;
|
||||
|
||||
/* The mask must be calculated after deciding whether the execute bit should be set. */
|
||||
}
|
||||
|
||||
if (!strv_isempty(d)) {
|
||||
_cleanup_free_ char *join = NULL;
|
||||
|
||||
|
@ -272,6 +317,7 @@ int parse_acl(const char *text, acl_t *ret_acl_access, acl_t *ret_acl_default, b
|
|||
}
|
||||
|
||||
*ret_acl_access = TAKE_PTR(a_acl);
|
||||
*ret_acl_access_exec = TAKE_PTR(e_acl);
|
||||
*ret_acl_default = TAKE_PTR(d_acl);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -15,7 +15,12 @@ int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry);
|
|||
int calc_acl_mask_if_needed(acl_t *acl_p);
|
||||
int add_base_acls_if_needed(acl_t *acl_p, const char *path);
|
||||
int acl_search_groups(const char* path, char ***ret_groups);
|
||||
int parse_acl(const char *text, acl_t *ret_acl_access, acl_t *ret_acl_default, bool want_mask);
|
||||
int parse_acl(
|
||||
const char *text,
|
||||
acl_t *ret_acl_access,
|
||||
acl_t *ret_acl_access_exec,
|
||||
acl_t *ret_acl_default,
|
||||
bool want_mask);
|
||||
int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *ret);
|
||||
int fd_add_uid_acl_permission(int fd, uid_t uid, unsigned mask);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue