2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-29 13:28:19 +00:00

Merge parser: add special casing for detached move mounts

upsteam move_mount mediation now allows for a detached (disconnected)
mount to be move mounted into a namespace.

Add support for this by detecting 'detached' as a keyword for the
source/device and using it to create a null match. Because existing
mount encoding using a null separator between the mount terms null
match followed by the null seperator will separate detached mounts
within the existing encoding.

```
Eg.
  mount detached -> /destination,
  mount options=(ro) fstype=ext4 detached -> /destination,
```

This is functionally equivalent to using
```
  mount "" -> /destination,
```
However using ```""``` does not provide any context that about what the rule is allowing or why so the ```detached``` form is preferred.

This is not a perfect solution, but is what can be currently supported
by the kernel without more LSM hooks.

On kernels that don't support detached mount detection, rules using
the detached source conditional will be ignored (never matched).

This encoding also allows the existing

```
  mount,
  mount options=(move),
  mount options=(move) -> /destination,
```

to continue to work with both detached and regular mounts on kernels
that support the move_mount() syscall.

Signed-off-by: John Johansen <john.johansen@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1561
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
This commit is contained in:
John Johansen 2025-04-05 08:26:33 +00:00
commit 2df4bbd39b
9 changed files with 71 additions and 18 deletions

View File

@ -772,8 +772,17 @@ int mnt_rule::gen_policy_remount(Profile &prof, int &count,
goto fail;
vec[0] = mntbuf.c_str();
} else {
if (!convert_entry(mntbuf, device))
if (device && strcmp(device, "detached") == 0) {
/* if (features_supports_detached_mount) ...
* not needed because this is equiv to ""
* which was preivously supported
*
* match nothing
*/
devbuf.clear();
} else if (!clear_and_convert_entry(devbuf, device)) {
goto fail;
}
vec[0] = mntbuf.c_str();
}
/* skip device */
@ -844,8 +853,12 @@ int mnt_rule::gen_policy_bind_mount(Profile &prof, int &count,
if (!convert_entry(mntbuf, mnt_point))
goto fail;
vec[0] = mntbuf.c_str();
if (!clear_and_convert_entry(devbuf, device))
if (device && strcmp(device, "detached") == 0) {
/* see note in move_mount. match nothing */
devbuf.clear();
} else if (!clear_and_convert_entry(devbuf, device)) {
goto fail;
}
vec[1] = devbuf.c_str();
/* skip type */
vec[2] = default_match_pattern;
@ -946,8 +959,12 @@ int mnt_rule::gen_policy_move_mount(Profile &prof, int &count,
if (!convert_entry(mntbuf, mnt_point))
goto fail;
vec[0] = mntbuf.c_str();
if (!clear_and_convert_entry(devbuf, device))
if (device && strcmp(device, "detached") == 0) {
/* see note in move_mount. match nothing */
devbuf.clear();
} else if (!clear_and_convert_entry(devbuf, device)) {
goto fail;
}
vec[1] = devbuf.c_str();
/* skip type */
vec[2] = default_match_pattern;
@ -987,8 +1004,12 @@ int mnt_rule::gen_policy_new_mount(Profile &prof, int &count,
if (!convert_entry(mntbuf, mnt_point))
goto fail;
vec[0] = mntbuf.c_str();
if (!clear_and_convert_entry(devbuf, device))
if (device && strcmp(device, "detached") == 0) {
/* see note in move mount. match nothing */
devbuf.clear();
} else if (!clear_and_convert_entry(devbuf, device)) {
goto fail;
}
vec[1] = devbuf.c_str();
typebuf.clear();
if (!build_list_val_expr(typebuf, dev_type))

View File

@ -321,6 +321,7 @@ extern int features_supports_inet;
extern int kernel_supports_policydb;
extern int kernel_supports_diff_encode;
extern int features_supports_mount;
extern bool features_supports_detached_mount;
extern int features_supports_dbus;
extern int features_supports_signal;
extern int features_supports_ptrace;

View File

@ -73,6 +73,7 @@ int features_supports_inet = 0; /* kernel supports inet network rules */
int features_supports_unix = 0; /* kernel supports unix socket rules */
int kernel_supports_policydb = 0; /* kernel supports new policydb */
int features_supports_mount = 0; /* kernel supports mount rules */
bool features_supports_detached_mount = false;
int features_supports_dbus = 0; /* kernel supports dbus rules */
int kernel_supports_diff_encode = 0; /* kernel supports diff_encode */
int features_supports_signal = 0; /* kernel supports signal rules */

View File

@ -956,6 +956,14 @@ void set_supported_features()
features_supports_mount = features_intersect(kernel_features,
policy_features,
"mount");
/*
* note: detached mounts are just a null condition, so previous
* mount rule encoding supports it, if the kernel supports
* it. So support for detached depends on mount intersect and
* kernel detached.
*/
features_supports_detached_mount = aa_features_supports(kernel_features,
"mount/move_mount/detached");
features_supports_dbus = features_intersect(kernel_features,
policy_features, "dbus");
features_supports_signal = features_intersect(kernel_features,

View File

@ -879,6 +879,11 @@ verify_binary_equality "'$p1'x'$p2' link rules slash filtering" \
@{BAR}=/mnt/
/t { $p2 link @{FOO}/foo -> @{BAR}/bar, }"
# Verify equality with mount detached source
verify_binary_equality "'$p1'x'$p2' mount detached vs empty source" \
"/t { $p1 mount \"\" -> /destination, }" \
"/t { $p2 mount detached -> /destination, }"
# This can potentially fail as ideally it requires a better dfa comparison
# routine as it can generates hormomorphic dfas. The enumeration of the

View File

@ -0,0 +1,7 @@
#
#=Description basic detached mount rule
#=EXRESULT PASS
#
/usr/bin/foo {
mount detached -> /foo,
}

View File

@ -0,0 +1,7 @@
#
#=Description detached mount rule with options
#=EXRESULT PASS
#
/usr/bin/foo {
mount options=(ro) fstype=ext4 detached -> /destination,
}

View File

@ -278,15 +278,15 @@ open_tree_test() {
runchecktest "MOVE_MOUNT (confined${desc}: mount options=(move) -> ${mnt_target}/,)" ${result} open_tree ${mnt_source} ${mnt_target} ${fsname}
remove_mnt
# genprofile cap:sys_admin "${qualifier}mount: detached -> ${mnt_target}/" ${additional_perms}
# mount ${loop_device} ${mnt_source}
# runchecktest "MOVE_MOUNT (confined${desc}: mount detached -> ${mnt_target}/,)" ${result} open_tree ${mnt_source} ${mnt_target} ${fsname}
# remove_mnt
genprofile cap:sys_admin "${qualifier}mount: detached -> ${mnt_target}/" ${additional_perms}
mount ${loop_device} ${mnt_source}
runchecktest "MOVE_MOUNT (confined${desc}: mount detached -> ${mnt_target}/,)" ${result} open_tree ${mnt_source} ${mnt_target} ${fsname}
remove_mnt
# genprofile cap:sys_admin "${qualifier}mount: options=(move) detached -> ${mnt_target}/" ${additional_perms}
# mount ${loop_device} ${mnt_source}
# runchecktest "MOVE_MOUNT (confined${desc}: mount options=(move) detached -> ${mnt_target}/,)" ${result} open_tree ${mnt_source} ${mnt_target} ${fsname}
# remove_mnt
genprofile cap:sys_admin "${qualifier}mount: options=(move) detached -> ${mnt_target}/" ${additional_perms}
mount ${loop_device} ${mnt_source}
runchecktest "MOVE_MOUNT (confined${desc}: mount options=(move) detached -> ${mnt_target}/,)" ${result} open_tree ${mnt_source} ${mnt_target} ${fsname}
remove_mnt
genprofile cap:sys_admin "${qualifier}mount: \"\" -> ${mnt_target}/" ${additional_perms}
mount ${loop_device} ${mnt_source}
@ -359,13 +359,13 @@ fsmount_test() {
runchecktest "MOVE_MOUNT (confined${desc}: mount options=(move) -> ${mnt_target}/,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname}
remove_mnt
# genprofile cap:sys_admin "${qualifier}mount: detached -> ${mnt_target}/" ${additional_perms}
# runchecktest "MOVE_MOUNT (confined${desc}: mount detached -> ${mnt_target}/,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname}
# remove_mnt
genprofile cap:sys_admin "${qualifier}mount: detached -> ${mnt_target}/" ${additional_perms}
runchecktest "MOVE_MOUNT (confined${desc}: mount detached -> ${mnt_target}/,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname}
remove_mnt
# genprofile cap:sys_admin "${qualifier}mount: options=(move) detached -> ${mnt_target}/" ${additional_perms}
# runchecktest "MOVE_MOUNT (confined${desc}: mount options=(move) detached -> ${mnt_target}/,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname}
# remove_mnt
genprofile cap:sys_admin "${qualifier}mount: options=(move) detached -> ${mnt_target}/" ${additional_perms}
runchecktest "MOVE_MOUNT (confined${desc}: mount options=(move) detached -> ${mnt_target}/,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname}
remove_mnt
genprofile cap:sys_admin "${qualifier}mount: \"\" -> ${mnt_target}/" ${additional_perms}
runchecktest "MOVE_MOUNT (confined${desc}: mount \"\" -> ${mnt_target}/,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname}

View File

@ -441,6 +441,9 @@ syntax_failure = (
'network/network_ok_17.sd',
'network/network_ok_45.sd',
'network/network_ok_46.sd',
# detached mount
'mount/ok_opt_86.sd',
)