mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-31 22:35:35 +00:00
Merge regression: test complain-mode operations on disconnected paths in mounts
Disconnected paths on lookups have caused actual permission denials, even when the loaded profile is in complain mode. This is a test that causes disconnections using mounts (both old and new API) and then verifies that a complain mode profile doesn't prevent operations with disconnected fds. Signed-off-by: Ryan Lee <ryan.lee@canonical.com> MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1568 Approved-by: John Johansen <john@jjmx.net> Merged-by: John Johansen <john@jjmx.net>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -237,6 +237,7 @@ tests/regression/apparmor/dbus_message
|
||||
tests/regression/apparmor/dbus_service
|
||||
tests/regression/apparmor/dbus_unrequested_reply
|
||||
tests/regression/apparmor/deleted
|
||||
tests/regression/apparmor/disconnected_mount_complain
|
||||
tests/regression/apparmor/env_check
|
||||
tests/regression/apparmor/environ
|
||||
tests/regression/apparmor/exec
|
||||
|
@@ -88,6 +88,23 @@ USE_SYSCTL:=$(shell echo $(SYSCTL_INCLUDE) | cpp -dM >/dev/null 2>/dev/null && e
|
||||
LINUX_MOUNT_INCLUDE="\#include <linux/mount.h>"
|
||||
HAVE_LINUX_MOUNT_H:=$(shell echo $(LINUX_MOUNT_INCLUDE) | cpp -dM >/dev/null 2>/dev/null && echo true)
|
||||
|
||||
# Perform detection of whether the new mount syscalls are available and whether
|
||||
# we'll need to provide the wrapper functions ourselves.
|
||||
# The syscalls themselves are available in Ubuntu Focal and later, with
|
||||
# associated constants available in linux/mount.h, but the wrapper functions are
|
||||
# only available in sys/mount.h in Ubuntu Noble and later.
|
||||
|
||||
# Test-compile the header with the test function selected as if it were a
|
||||
# regular .c file:
|
||||
# * -Wimplicit-function-declaration to warn upon function prototype not existing in header
|
||||
# * -Werror to convert that warning into a hard error
|
||||
# * -x c to force compilation to an object file instead of a precompiled header
|
||||
HAVE_NEW_MOUNT_PROTOS=$(shell gcc -c -o /dev/null -Wimplicit-function-declaration -Werror -x c -DCHECK_FOR_NEW_MOUNT_PROTOTYPES mount_syscall_iface.h && echo true)
|
||||
|
||||
ifneq ($(HAVE_NEW_MOUNT_PROTOS),true)
|
||||
CFLAGS += -DVENDORED_NEW_MOUNT_PROTOTYPES
|
||||
endif
|
||||
|
||||
CFLAGS += -g -O0 $(EXTRA_WARNINGS)
|
||||
|
||||
SRC=access.c \
|
||||
@@ -111,6 +128,7 @@ SRC=access.c \
|
||||
complain.c \
|
||||
coredump.c \
|
||||
deleted.c \
|
||||
disconnected_mount_complain.c \
|
||||
environ.c \
|
||||
env_check.c \
|
||||
exec.c \
|
||||
@@ -179,6 +197,8 @@ endif
|
||||
#only do move_mount test if we have linux/mount.h
|
||||
ifeq ($(HAVE_LINUX_MOUNT_H),true)
|
||||
SRC+=move_mount.c
|
||||
else
|
||||
CFLAGS += -DSKIP_NEW_MOUNT_TESTING
|
||||
endif
|
||||
|
||||
#only do sysctl syscall test if defines installed and OR supported by the
|
||||
@@ -277,6 +297,7 @@ TESTS=aa_exec \
|
||||
complain \
|
||||
coredump \
|
||||
deleted \
|
||||
disconnected_mount_complain \
|
||||
e2e \
|
||||
environ \
|
||||
exec \
|
||||
|
404
tests/regression/apparmor/disconnected_mount_complain.c
Normal file
404
tests/regression/apparmor/disconnected_mount_complain.c
Normal file
@@ -0,0 +1,404 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "mount_syscall_iface.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <sys/apparmor.h>
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DEBUG_PRINTF(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_PRINTF(...)
|
||||
#endif
|
||||
|
||||
off_t file_size(int file_fd) {
|
||||
struct stat file_fd_stat;
|
||||
if (fstat(file_fd, &file_fd_stat) == -1) {
|
||||
// Immediate return to preserve errno
|
||||
return -1;
|
||||
} else {
|
||||
return file_fd_stat.st_size;
|
||||
}
|
||||
}
|
||||
|
||||
int fork_and_execvat(int exec_fd, char* const* argv) {
|
||||
DEBUG_PRINTF("fork()ing to exec child binary\n");
|
||||
int pid = fork();
|
||||
if (pid == 0) {
|
||||
fexecve(exec_fd, argv, environ);
|
||||
perror("FAIL: Could not execveat binary");
|
||||
close(exec_fd);
|
||||
// Special exit that does not trigger any atexit handlers
|
||||
_exit(1);
|
||||
} else if (pid == -1) {
|
||||
perror("FAIL: Could not fork()");
|
||||
return -1;
|
||||
} else {
|
||||
DEBUG_PRINTF("Waiting on child\n");
|
||||
int child_status;
|
||||
wait(&child_status);
|
||||
if (!WIFEXITED(child_status)) {
|
||||
fprintf(stderr, "FAIL: child did not exit normally\n");
|
||||
}
|
||||
return WEXITSTATUS(child_status);
|
||||
}
|
||||
}
|
||||
|
||||
int test_with_old_style_mount() {
|
||||
// Set up directory fds for future reference
|
||||
DEBUG_PRINTF("Opening fds for shadowed and shadowing directories\n");
|
||||
int shadowed_dirfd = openat(AT_FDCWD, "shadowed", O_DIRECTORY | O_PATH);
|
||||
if (shadowed_dirfd == -1) {
|
||||
perror("FAIL: could not open shadowed dirfd");
|
||||
return 1;
|
||||
}
|
||||
int shadowing_dirfd = openat(AT_FDCWD, "shadowing", O_DIRECTORY | O_PATH);
|
||||
if (shadowing_dirfd == -1) {
|
||||
perror("FAIL: could not open shadowing dirfd");
|
||||
close(shadowed_dirfd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("Opening fds for files in shadowed dir\n");
|
||||
int shadowed_file_fd = openat(shadowed_dirfd, "write_file", O_CREAT | O_RDWR, 0644);
|
||||
if (shadowed_file_fd == -1) {
|
||||
perror("FAIL: could not create file in shadowed dir");
|
||||
close(shadowed_dirfd);
|
||||
close(shadowing_dirfd);
|
||||
return 1;
|
||||
}
|
||||
int shadowed_exec_fd = openat(shadowed_dirfd, "true", O_PATH);
|
||||
if (shadowed_exec_fd == -1) {
|
||||
perror("FAIL: could not open executable file in shadowed dir");
|
||||
close(shadowed_file_fd);
|
||||
close(shadowing_dirfd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("Write something to file\n");
|
||||
int rc = 0;
|
||||
// Write something into the file
|
||||
if (write(shadowed_file_fd, "first\n", 6) == -1) {
|
||||
perror("FAIL: could not write to file before mount");
|
||||
rc |= 1;
|
||||
goto cleanup_fds;
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("Unshare mount ns and bind mount over shadowed dir\n");
|
||||
// Call unshare() to step into a new mount namespace
|
||||
if (unshare(CLONE_NEWNS) == -1) {
|
||||
perror("FAIL: could not unshare mount namespace");
|
||||
rc |= 1;
|
||||
goto cleanup_fds;
|
||||
}
|
||||
// Mount over directory, shadowing the path corresponding to the fd
|
||||
if (mount("shadowing", "shadowed", NULL, MS_BIND, NULL) == -1) {
|
||||
perror("FAIL: could not bind mount shadowing over shadowed\n");
|
||||
rc |= 1;
|
||||
goto cleanup_fds;
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("Write something to (now disconnected) file\n");
|
||||
// Write something into the file, again (after mount)
|
||||
if (write(shadowed_file_fd, "second\n", 7) == -1) {
|
||||
perror("FAIL: could not write to file after mount");
|
||||
rc |= 1;
|
||||
goto cleanup_mount;
|
||||
}
|
||||
|
||||
// Now attempt to stat and read from the fd
|
||||
DEBUG_PRINTF("Stat disconnected file\n");
|
||||
if (lseek(shadowed_file_fd, 0, SEEK_SET) == -1) {
|
||||
perror("FAIL: could not lseek to start of file");
|
||||
rc |= 1;
|
||||
goto cleanup_mount;
|
||||
}
|
||||
off_t shadowed_file_size = file_size(shadowed_file_fd);
|
||||
if (shadowed_file_size == -1) {
|
||||
perror("FAIL: could not fstat file");
|
||||
rc |= 1;
|
||||
goto cleanup_mount;
|
||||
} else {
|
||||
DEBUG_PRINTF("File size is %ld\n", shadowed_file_size);
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("Read from disconnected file\n");
|
||||
char *file_contents_buf = calloc(shadowed_file_size+1, sizeof(char));
|
||||
if (read(shadowed_file_fd, file_contents_buf, shadowed_file_size) == -1) {
|
||||
perror("FAIL: could not read from file after mount");
|
||||
rc |= 1;
|
||||
} else {
|
||||
DEBUG_PRINTF("Read file contents:\n%s\n", file_contents_buf);
|
||||
}
|
||||
free(file_contents_buf);
|
||||
file_contents_buf = NULL;
|
||||
|
||||
if (rc != 0) {
|
||||
goto cleanup_mount;
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("execvat disconnected binary file\n");
|
||||
char* const new_argv[] = {"true", NULL};
|
||||
if (fork_and_execvat(shadowed_exec_fd, new_argv) != 0) {
|
||||
perror("FAIL: child exited with non-zero status code");
|
||||
rc |= 1;
|
||||
goto cleanup_mount;
|
||||
}
|
||||
|
||||
cleanup_mount:
|
||||
if (umount("shadowed") == -1) {
|
||||
perror("FAIL: could not unmount bind mount");
|
||||
rc |= 1;
|
||||
}
|
||||
cleanup_fds:
|
||||
close(shadowed_file_fd);
|
||||
close(shadowing_dirfd);
|
||||
close(shadowed_exec_fd);
|
||||
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "PASS\n");
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifndef SKIP_NEW_MOUNT_TESTING
|
||||
int test_with_open_tree_mount() {
|
||||
DEBUG_PRINTF("Unshare mount ns\n");
|
||||
// Call unshare() to step into a new mount namespace
|
||||
if (unshare(CLONE_NEWNS) == -1) {
|
||||
perror("FAIL: could not unshare mount namespace");
|
||||
return 1;
|
||||
}
|
||||
DEBUG_PRINTF("bind mount shadowed using new mount API\n");
|
||||
int fd_bind_mnt = open_tree(AT_FDCWD, "shadowed", OPEN_TREE_CLONE);
|
||||
if (fd_bind_mnt == -1) {
|
||||
perror("FAIL: could not open_tree bind mount to shadowed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rc = 0;
|
||||
|
||||
DEBUG_PRINTF("bind mount nested preparation and attachment\n");
|
||||
int fd_inception_bind_mnt = open_tree(AT_FDCWD, "shadowing", OPEN_TREE_CLONE);
|
||||
if (fd_inception_bind_mnt == -1) {
|
||||
perror("FAIL: could not open_tree bind mount to be nested");
|
||||
close(fd_bind_mnt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int move_status = move_mount(fd_inception_bind_mnt, "", fd_bind_mnt, "inner_dir", MOVE_MOUNT_F_EMPTY_PATH);
|
||||
/*
|
||||
* In the (6.11) kernel, move_mount has the following sequence of checks:
|
||||
* - Capability check for doing mounts at all (EPERM)
|
||||
* - Sanity checks for the flags (EINVAL)
|
||||
* - Verification that unmount wouldn't be required for the pathname
|
||||
* - LSM hook invocation for the syscall via security_move_mount
|
||||
* - AppArmor: profile rule based denials (EACCES, EPERM), with some
|
||||
* EINVALs that would trigger on external kernel changes that would
|
||||
* require corresponding changes to the AppArmor kernel side
|
||||
* - do_move_mount(), which does some further sanity checks, such as
|
||||
* - checking that the mountpoint is in our namespace (EINVAL)
|
||||
* - checking that the old path is mounted (EINVAL)
|
||||
* - checking that the new mount location would not create a loop (ELOOP)
|
||||
*
|
||||
* The operation below should trigger EINVAL from do_move_mount due to
|
||||
* either the mountpoint not in the namespace (if fd_bind_mount was
|
||||
* set up with OPEN_TREE_CLONE) or the old path not being mounted (if not).
|
||||
*
|
||||
* TODO: disambiguating EINVALs from AppArmor vs outside of it would require
|
||||
* audit log inspection
|
||||
*/
|
||||
if (move_status == -1 && errno != EINVAL) {
|
||||
perror("FAIL: could not attach nested bind mount");
|
||||
rc |= 1;
|
||||
goto cleanup_mount;
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("open file in not-actually-nested bind mount\n");
|
||||
int inception_fd = openat(fd_inception_bind_mnt, "cornh", O_RDONLY);
|
||||
if (inception_fd == -1) {
|
||||
perror("FAIL: could not open file in not-actually-nested bind mount");
|
||||
rc |= 1;
|
||||
goto cleanup_mount;
|
||||
}
|
||||
// Can close the fd now, as we only wanted to check successful open
|
||||
close(inception_fd);
|
||||
|
||||
DEBUG_PRINTF("open executable file in bind mount\n");
|
||||
int shadowed_exec_fd = openat(fd_bind_mnt, "true", O_PATH);
|
||||
if (shadowed_exec_fd == -1) {
|
||||
perror("FAIL: could not open executable file in bind mount");
|
||||
rc |= 1;
|
||||
goto cleanup_file_fd;
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("execvat bind mount binary file\n");
|
||||
char* const new_argv[] = {"true", NULL};
|
||||
if (fork_and_execvat(shadowed_exec_fd, new_argv) != 0) {
|
||||
perror("FAIL: child exited with non-zero status code");
|
||||
rc |= 1;
|
||||
goto cleanup_file_fd;
|
||||
}
|
||||
|
||||
cleanup_file_fd:
|
||||
close(shadowed_exec_fd);
|
||||
cleanup_mount:
|
||||
close(fd_inception_bind_mnt);
|
||||
close(fd_bind_mnt);
|
||||
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "PASS\n");
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int test_with_fsmount(const char *source) {
|
||||
DEBUG_PRINTF("Unshare mount ns\n");
|
||||
// Call unshare() to step into a new mount namespace
|
||||
if (unshare(CLONE_NEWNS) == -1) {
|
||||
perror("FAIL: could not unshare mount namespace");
|
||||
return 1;
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("Fsopen ext4\n");
|
||||
int fsopen_fd = fsopen("ext4", FSOPEN_CLOEXEC);
|
||||
if (fsopen_fd == -1) {
|
||||
perror("FAIL: fsopen() failed");
|
||||
return -1;
|
||||
}
|
||||
int rc = 0;
|
||||
|
||||
DEBUG_PRINTF("Fsconfig source\n");
|
||||
int fsconfig_src_status = fsconfig(fsopen_fd, FSCONFIG_SET_STRING, "source", source, 0);
|
||||
if (fsconfig_src_status == -1) {
|
||||
perror("FAIL: fsconfig() of source failed");
|
||||
rc |= 1;
|
||||
goto fsopen_cleanup;
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("Fsconfig create\n");
|
||||
int fsconfig_creat_status = fsconfig(fsopen_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
|
||||
if (fsconfig_creat_status == -1) {
|
||||
perror("FAIL: fsconfig() create failed");
|
||||
rc |= 1;
|
||||
goto fsopen_cleanup;
|
||||
}
|
||||
|
||||
// The LSM lacks hooks for this so no audit gets generated for fsmount
|
||||
DEBUG_PRINTF("Fsmount\n");
|
||||
int fsmount_fd = fsmount(fsopen_fd, FSMOUNT_CLOEXEC, 0);
|
||||
if (fsmount_fd == -1) {
|
||||
perror("FAIL: fsmount() failed");
|
||||
rc |= 1;
|
||||
goto fsopen_cleanup;
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("Create and populate shell script in fsmount\n");
|
||||
int inner_fd_w = openat(fsmount_fd, "absolute_cinema.sh",
|
||||
O_CREAT | O_WRONLY | O_CLOEXEC, 0755);
|
||||
if (inner_fd_w == -1) {
|
||||
perror("FAIL: could not create file in fsmount");
|
||||
rc |= 1;
|
||||
goto fsmount_cleanup;
|
||||
}
|
||||
if (write(inner_fd_w, "#!/bin/sh\ntrue\n", 16) == -1) {
|
||||
perror("FAIL: could not write to file in fsmount");
|
||||
rc |= 1;
|
||||
close(inner_fd_w);
|
||||
goto fsmount_cleanup;
|
||||
}
|
||||
|
||||
close(inner_fd_w);
|
||||
// This fd must not be O_CLOEXEC or the execveat will fail with ENOENT
|
||||
int inner_fd_path = openat(fsmount_fd, "absolute_cinema.sh", O_PATH);
|
||||
if (inner_fd_path == -1) {
|
||||
perror("FAIL: could not reopen file in fsmount");
|
||||
rc |= 1;
|
||||
goto file_cleanup;
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("Execute shell script in fsmount\n");
|
||||
char* const script_argv[] = {"absolute_cinema.sh", NULL};
|
||||
if (fork_and_execvat(inner_fd_path, script_argv) != 0) {
|
||||
perror("FAIL: script execution in fsmount failure");
|
||||
rc |= 1;
|
||||
goto file_cleanup;
|
||||
}
|
||||
|
||||
file_cleanup:
|
||||
close(inner_fd_path);
|
||||
fsmount_cleanup:
|
||||
close(fsmount_fd);
|
||||
fsopen_cleanup:
|
||||
close(fsopen_fd);
|
||||
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "PASS\n");
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 3 && argc != 4) {
|
||||
#ifdef SKIP_NEW_MOUNT_TESTING
|
||||
fprintf(stderr, "FAIL: Usage: disconnected_mount_complain [WORKDIR] old");
|
||||
#else
|
||||
fprintf(stderr, "FAIL: Usage: disconnected_mount_complain [WORKDIR] (old|open_tree|fsmount) [device_if_fsmount]");
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
{
|
||||
char *label;
|
||||
char *mode;
|
||||
if (aa_getcon(&label, &mode) == -1) {
|
||||
perror("FAIL: could not get current AppArmor confinement");
|
||||
} else {
|
||||
DEBUG_PRINTF("AppArmor confinement label=%s mode=%s\n", label, mode);
|
||||
free(label);
|
||||
label = NULL;
|
||||
mode = NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (chdir(argv[1]) != 0) {
|
||||
perror("FAIL: could not chdir to workdir");
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(argv[2], "old") == 0) {
|
||||
return test_with_old_style_mount();
|
||||
}
|
||||
#ifndef SKIP_NEW_MOUNT_TESTING
|
||||
else if (strcmp(argv[2], "open_tree") == 0) {
|
||||
return test_with_open_tree_mount();
|
||||
} else if (strcmp(argv[2], "fsmount") == 0) {
|
||||
if (argc != 4) {
|
||||
fprintf(stderr, "FAIL: Usage: disconnected_mount_complain [WORKDIR] fsmount device");
|
||||
return 1;
|
||||
}
|
||||
return test_with_fsmount(argv[3]);
|
||||
} else {
|
||||
fprintf(stderr, "FAIL: second argument must be 'old', 'open_tree', or 'fsmount'\n");
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
else {
|
||||
fprintf(stderr, "FAIL: second argument must be 'old'\n");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
}
|
65
tests/regression/apparmor/disconnected_mount_complain.sh
Normal file
65
tests/regression/apparmor/disconnected_mount_complain.sh
Normal file
@@ -0,0 +1,65 @@
|
||||
#! /bin/bash
|
||||
# Copyright (C) 2025 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, version 2 of the
|
||||
# License.
|
||||
|
||||
#=NAME disconnected_mount_complain
|
||||
#=DESCRIPTION
|
||||
# Verifies that complain-mode profiles work as expected and do not block
|
||||
# disconnected path operations
|
||||
#=END
|
||||
|
||||
# This test suite will need the patchset posted to
|
||||
# https://lists.ubuntu.com/archives/apparmor/2025-March/013533.html
|
||||
# to be applied to the kernel in order to pass
|
||||
|
||||
pwd=`dirname $0`
|
||||
pwd=`cd $pwd ; /bin/pwd`
|
||||
|
||||
bin=$pwd
|
||||
|
||||
. "$bin/prologue.inc"
|
||||
|
||||
shadowed_target=$tmpdir/shadowed
|
||||
shadowing_dir=$tmpdir/shadowing
|
||||
backing_file_fsmount="$tmpdir/loop_file"
|
||||
|
||||
mkdir "$shadowed_target"
|
||||
# Complications because true is also a shell builtin
|
||||
cp "$(type -P true)" "${shadowed_target}/true"
|
||||
mkdir "${shadowed_target}/inner_dir"
|
||||
|
||||
mkdir "$shadowing_dir"
|
||||
# the cornh file has 5 letters: the (h|newline) is silent, and you can't see it
|
||||
echo "corn" > "${shadowing_dir}/cornh"
|
||||
|
||||
genprofile -C cap:sys_admin
|
||||
runchecktest "Complain mode profile and disconnected path mounts (mount(2))" pass $tmpdir old
|
||||
|
||||
# Use the presence of move_mount as a proxy for new mount syscall availability
|
||||
if [ ! -f "$bin/move_mount" ]; then
|
||||
echo " WARNING: move_mount binary was not built, skipping open_tree test ..."
|
||||
else
|
||||
runchecktest "Complain mode profile and disconnected path mounts (open_tree(2))" pass $tmpdir open_tree
|
||||
fi
|
||||
|
||||
rm -r "$shadowed_target"
|
||||
rm -r "$shadowing_dir"
|
||||
|
||||
if [ ! -f "$bin/move_mount" ]; then
|
||||
echo " WARNING: move_mount binary was not built, skipping fsmount test ..."
|
||||
else
|
||||
fallocate -l 512K "${backing_file_fsmount}"
|
||||
mkfs.ext4 -F "${backing_file_fsmount}" > /dev/null 2> /dev/null
|
||||
|
||||
losetup -f "${backing_file_fsmount}" || fatalerror 'Unable to set up loop device'
|
||||
loop_device="$(/sbin/losetup -n -O NAME -l -j "${backing_file_fsmount}")"
|
||||
|
||||
runchecktest "Complain mode profile and disconnected path mounts (fsmount(2))" pass $tmpdir fsmount "${loop_device}"
|
||||
|
||||
losetup -d "${loop_device}"
|
||||
rm "${backing_file_fsmount}"
|
||||
fi
|
158
tests/regression/apparmor/mount_syscall_iface.h
Normal file
158
tests/regression/apparmor/mount_syscall_iface.h
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* A function that compile-tests if sys/mount.h has prototypes for the
|
||||
* new mount syscalls (we don't care about the args as long as they're
|
||||
* the right type)
|
||||
*/
|
||||
#ifdef CHECK_FOR_NEW_MOUNT_PROTOTYPES
|
||||
#include <sys/mount.h>
|
||||
int test_for_open_tree() {
|
||||
int ot = open_tree(-1, "path", 0);
|
||||
int mm = move_mount(-1, "from", -1, "to", 0);
|
||||
int fsm = fsmount(-1, 0, 0);
|
||||
int fsc = fsconfig(-1, 0, "key", "value", 0);
|
||||
int fso = fsopen("fstype", 0);
|
||||
return ot ^ mm ^ fsm ^ fsc ^ fso;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Include <sys/mount.h> for the mount(2) and umount(2) prototypes
|
||||
#include <sys/mount.h>
|
||||
|
||||
// If needed, include our vendored prototypes for the new syscalls
|
||||
#if defined(VENDORED_NEW_MOUNT_PROTOTYPES) && !defined(SKIP_NEW_MOUNT_TESTING)
|
||||
|
||||
#include <sys/syscall.h>
|
||||
#include <linux/mount.h>
|
||||
|
||||
/* fs/namespace.c
|
||||
*
|
||||
* SYSCALL_DEFINE3(open_tree, int, dfd, const char __user *, filename,
|
||||
* unsigned, flags)
|
||||
*/
|
||||
static inline int open_tree(int dirfd, const char *filename, unsigned int flags)
|
||||
{
|
||||
return syscall(SYS_open_tree, dirfd, filename, flags);
|
||||
}
|
||||
|
||||
/* fs/namespace.c
|
||||
*
|
||||
* SYSCALL_DEFINE5(move_mount,
|
||||
* int, from_dfd, const char __user *, from_pathname,
|
||||
* int, to_dfd, const char __user *, to_pathname,
|
||||
* unsigned int, flags)
|
||||
*
|
||||
* Move a mount from one place to another. In combination with
|
||||
* fsopen()/fsmount() this is used to install a new mount and in combination
|
||||
* with open_tree(OPEN_TREE_CLONE [| AT_RECURSIVE]) it can be used to copy
|
||||
* a mount subtree.
|
||||
*
|
||||
* Note the flags value is a combination of MOVE_MOUNT_* flags.
|
||||
*
|
||||
* #define MOVE_MOUNT_F_SYMLINKS 0x00000001 // Follow symlinks on from path
|
||||
* #define MOVE_MOUNT_F_AUTOMOUNTS 0x00000002 // Follow automounts on from path
|
||||
* #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 // Empty from path permitted
|
||||
* #define MOVE_MOUNT_T_SYMLINKS 0x00000010 // Follow symlinks on to path
|
||||
* #define MOVE_MOUNT_T_AUTOMOUNTS 0x00000020//Follow automounts on to path
|
||||
* #define MOVE_MOUNT_T_EMPTY_PATH 0x00000040 // Empty to path permitted
|
||||
* #define MOVE_MOUNT_SET_GROUP 0x00000100 // Set sharing group instead
|
||||
* #define MOVE_MOUNT_BENEATH 0x00000200 // Mount beneath top mount
|
||||
* #define MOVE_MOUNT__MASK 0x00000377
|
||||
*/
|
||||
static inline int move_mount(int from_dirfd, const char *from_pathname,
|
||||
int to_dirfd, const char *to_pathname,
|
||||
unsigned int flags)
|
||||
{
|
||||
return syscall(SYS_move_mount, from_dirfd, from_pathname,
|
||||
to_dirfd, to_pathname, flags);
|
||||
}
|
||||
|
||||
/* fs/namespace.c
|
||||
*
|
||||
* SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags,
|
||||
* unsigned int, attr_flags)
|
||||
*
|
||||
* Create a kernel mount representation for a new, prepared superblock
|
||||
* (specified by fs_fd) and attach to an open_tree-like file descriptor.
|
||||
*
|
||||
* #define FSMOUNT_CLOEXEC 0x00000001
|
||||
*/
|
||||
static inline int fsmount(int fs_fd, unsigned int flags,
|
||||
unsigned int attr_flags)
|
||||
{
|
||||
return syscall(SYS_fsmount, fs_fd, flags, attr_flags);
|
||||
}
|
||||
|
||||
/* fs/fsopen.c
|
||||
*
|
||||
* SYSCALL_DEFINE5(fsconfig,
|
||||
* int, fd,
|
||||
* unsigned int, cmd,
|
||||
* const char __user *, _key,
|
||||
* const void __user *, _value,
|
||||
* int, aux)
|
||||
*
|
||||
* @fd: The filesystem context to act upon
|
||||
* @cmd: The action to take
|
||||
* @_key: Where appropriate, the parameter key to set
|
||||
* @_value: Where appropriate, the parameter value to set
|
||||
* @aux: Additional information for the value
|
||||
*
|
||||
* This system call is used to set parameters on a context, including
|
||||
* superblock settings, data source and security labelling.
|
||||
*
|
||||
* Actions include triggering the creation of a superblock and the
|
||||
* reconfiguration of the superblock attached to the specified context.
|
||||
*
|
||||
* When setting a parameter, @cmd indicates the type of value being proposed
|
||||
* and @_key indicates the parameter to be altered.
|
||||
*
|
||||
* @_value and @aux are used to specify the value, should a value be required:
|
||||
*
|
||||
* (*) fsconfig_set_flag: No value is specified. The parameter must be boolean
|
||||
* in nature. The key may be prefixed with "no" to invert the
|
||||
* setting. @_value must be NULL and @aux must be 0.
|
||||
*
|
||||
* (*) fsconfig_set_string: A string value is specified. The parameter can be
|
||||
* expecting boolean, integer, string or take a path. A conversion to an
|
||||
* appropriate type will be attempted (which may include looking up as a
|
||||
* path). @_value points to a NUL-terminated string and @aux must be 0.
|
||||
*
|
||||
* (*) fsconfig_set_binary: A binary blob is specified. @_value points to the
|
||||
* blob and @aux indicates its size. The parameter must be expecting a
|
||||
* blob.
|
||||
*
|
||||
* (*) fsconfig_set_path: A non-empty path is specified. The parameter must be
|
||||
* expecting a path object. @_value points to a NUL-terminated string that
|
||||
* is the path and @aux is a file descriptor at which to start a relative
|
||||
* lookup or AT_FDCWD.
|
||||
*
|
||||
* (*) fsconfig_set_path_empty: As fsconfig_set_path, but with AT_EMPTY_PATH
|
||||
* implied.
|
||||
*
|
||||
* (*) fsconfig_set_fd: An open file descriptor is specified. @_value must be
|
||||
* NULL and @aux indicates the file descriptor.
|
||||
*/
|
||||
static inline int fsconfig(int fs_fd, unsigned int cmd, const char *key,
|
||||
const void *value, int aux)
|
||||
{
|
||||
return syscall(SYS_fsconfig, fs_fd, cmd, key, value, aux);
|
||||
}
|
||||
|
||||
/* fs/fsopen.c
|
||||
*
|
||||
* SYSCALL_DEFINE2(fsopen, const char __user *, _fs_name, unsigned int, flags)
|
||||
*
|
||||
* Open a filesystem by name so that it can be configured for mounting.
|
||||
*
|
||||
* We are allowed to specify a container in which the filesystem will be
|
||||
* opened, thereby indicating which namespaces will be used (notably, which
|
||||
* network namespace will be used for network filesystems).
|
||||
*
|
||||
* #define FSOPEN_CLOEXEC 0x00000001
|
||||
*/
|
||||
static inline int fsopen(const char *fs_name, unsigned int flags)
|
||||
{
|
||||
return syscall(SYS_fsopen, fs_name, flags);
|
||||
}
|
||||
|
||||
#endif
|
@@ -5,150 +5,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/types.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#ifndef open_tree
|
||||
/* fs/namespace.c
|
||||
*
|
||||
* SYSCALL_DEFINE3(open_tree, int, dfd, const char __user *, filename,
|
||||
* unsigned, flags)
|
||||
*/
|
||||
static inline int open_tree(int dirfd, const char *filename, unsigned int flags)
|
||||
{
|
||||
return syscall(SYS_open_tree, dirfd, filename, flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef move_mount
|
||||
/* fs/namespace.c
|
||||
*
|
||||
* SYSCALL_DEFINE5(move_mount,
|
||||
* int, from_dfd, const char __user *, from_pathname,
|
||||
* int, to_dfd, const char __user *, to_pathname,
|
||||
* unsigned int, flags)
|
||||
*
|
||||
* Move a mount from one place to another. In combination with
|
||||
* fsopen()/fsmount() this is used to install a new mount and in combination
|
||||
* with open_tree(OPEN_TREE_CLONE [| AT_RECURSIVE]) it can be used to copy
|
||||
* a mount subtree.
|
||||
*
|
||||
* Note the flags value is a combination of MOVE_MOUNT_* flags.
|
||||
*
|
||||
* #define MOVE_MOUNT_F_SYMLINKS 0x00000001 // Follow symlinks on from path
|
||||
* #define MOVE_MOUNT_F_AUTOMOUNTS 0x00000002 // Follow automounts on from path
|
||||
* #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 // Empty from path permitted
|
||||
* #define MOVE_MOUNT_T_SYMLINKS 0x00000010 // Follow symlinks on to path
|
||||
* #define MOVE_MOUNT_T_AUTOMOUNTS 0x00000020//Follow automounts on to path
|
||||
* #define MOVE_MOUNT_T_EMPTY_PATH 0x00000040 // Empty to path permitted
|
||||
* #define MOVE_MOUNT_SET_GROUP 0x00000100 // Set sharing group instead
|
||||
* #define MOVE_MOUNT_BENEATH 0x00000200 // Mount beneath top mount
|
||||
* #define MOVE_MOUNT__MASK 0x00000377
|
||||
*/
|
||||
static inline int move_mount(int from_dirfd, const char *from_pathname,
|
||||
int to_dirfd, const char *to_pathname,
|
||||
unsigned int flags)
|
||||
{
|
||||
return syscall(SYS_move_mount, from_dirfd, from_pathname,
|
||||
to_dirfd, to_pathname, flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef fsmount
|
||||
/* fs/namespace.c
|
||||
*
|
||||
* SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags,
|
||||
* unsigned int, attr_flags)
|
||||
*
|
||||
* Create a kernel mount representation for a new, prepared superblock
|
||||
* (specified by fs_fd) and attach to an open_tree-like file descriptor.
|
||||
*
|
||||
* #define FSMOUNT_CLOEXEC 0x00000001
|
||||
*/
|
||||
static inline int fsmount(int fs_fd, unsigned int flags,
|
||||
unsigned int attr_flags)
|
||||
{
|
||||
return syscall(SYS_fsmount, fs_fd, flags, attr_flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef fsconfig
|
||||
/* fs/fsopen.c
|
||||
*
|
||||
* SYSCALL_DEFINE5(fsconfig,
|
||||
* int, fd,
|
||||
* unsigned int, cmd,
|
||||
* const char __user *, _key,
|
||||
* const void __user *, _value,
|
||||
* int, aux)
|
||||
*
|
||||
* @fd: The filesystem context to act upon
|
||||
* @cmd: The action to take
|
||||
* @_key: Where appropriate, the parameter key to set
|
||||
* @_value: Where appropriate, the parameter value to set
|
||||
* @aux: Additional information for the value
|
||||
*
|
||||
* This system call is used to set parameters on a context, including
|
||||
* superblock settings, data source and security labelling.
|
||||
*
|
||||
* Actions include triggering the creation of a superblock and the
|
||||
* reconfiguration of the superblock attached to the specified context.
|
||||
*
|
||||
* When setting a parameter, @cmd indicates the type of value being proposed
|
||||
* and @_key indicates the parameter to be altered.
|
||||
*
|
||||
* @_value and @aux are used to specify the value, should a value be required:
|
||||
*
|
||||
* (*) fsconfig_set_flag: No value is specified. The parameter must be boolean
|
||||
* in nature. The key may be prefixed with "no" to invert the
|
||||
* setting. @_value must be NULL and @aux must be 0.
|
||||
*
|
||||
* (*) fsconfig_set_string: A string value is specified. The parameter can be
|
||||
* expecting boolean, integer, string or take a path. A conversion to an
|
||||
* appropriate type will be attempted (which may include looking up as a
|
||||
* path). @_value points to a NUL-terminated string and @aux must be 0.
|
||||
*
|
||||
* (*) fsconfig_set_binary: A binary blob is specified. @_value points to the
|
||||
* blob and @aux indicates its size. The parameter must be expecting a
|
||||
* blob.
|
||||
*
|
||||
* (*) fsconfig_set_path: A non-empty path is specified. The parameter must be
|
||||
* expecting a path object. @_value points to a NUL-terminated string that
|
||||
* is the path and @aux is a file descriptor at which to start a relative
|
||||
* lookup or AT_FDCWD.
|
||||
*
|
||||
* (*) fsconfig_set_path_empty: As fsconfig_set_path, but with AT_EMPTY_PATH
|
||||
* implied.
|
||||
*
|
||||
* (*) fsconfig_set_fd: An open file descriptor is specified. @_value must be
|
||||
* NULL and @aux indicates the file descriptor.
|
||||
*/
|
||||
static inline int fsconfig(int fs_fd, unsigned int cmd, const char *key,
|
||||
const void *value, int aux)
|
||||
{
|
||||
return syscall(SYS_fsconfig, fs_fd, cmd, key, value, aux);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef fsopen
|
||||
/* fs/fsopen.c
|
||||
*
|
||||
* SYSCALL_DEFINE2(fsopen, const char __user *, _fs_name, unsigned int, flags)
|
||||
*
|
||||
* Open a filesystem by name so that it can be configured for mounting.
|
||||
*
|
||||
* We are allowed to specify a container in which the filesystem will be
|
||||
* opened, thereby indicating which namespaces will be used (notably, which
|
||||
* network namespace will be used for network filesystems).
|
||||
*
|
||||
* #define FSOPEN_CLOEXEC 0x00000001
|
||||
*/
|
||||
static inline int fsopen(const char *fs_name, unsigned int flags)
|
||||
{
|
||||
return syscall(SYS_fsopen, fs_name, flags);
|
||||
}
|
||||
#endif
|
||||
#include "mount_syscall_iface.h"
|
||||
|
||||
int do_open_tree_move_mount(const char *source, const char *target)
|
||||
{
|
||||
|
@@ -20,6 +20,7 @@ environment:
|
||||
TEST/dbus_service: 1
|
||||
TEST/dbus_unrequested_reply: 1
|
||||
TEST/deleted: 1
|
||||
TEST/disconnected_mount_complain: 1
|
||||
TEST/e2e: 1
|
||||
TEST/environ: 1
|
||||
TEST/exec: 1
|
||||
@@ -86,6 +87,7 @@ environment:
|
||||
XFAIL/attach_disconnected: opensuse-cloud-tumbleweed debian-cloud-12 debian-cloud-13 ubuntu-cloud-22.04
|
||||
# Error: unix_fd_server failed. Test 'fd passing; unconfined client' was expected to 'pass'. Reason for failure 'FAIL - bind failed: Permission denied'
|
||||
# Error: unix_fd_server failed. Test 'fd passing; confined client w/ rw' was expected to 'pass'. Reason for failure 'FAIL - bind failed: Permission denied'
|
||||
XFAIL/disconnected_mount_complain: opensuse-cloud-tumbleweed debian-cloud-12 debian-cloud-13 ubuntu-cloud-22.04 ubuntu-cloud-24.04 ubuntu-cloud-24.10 fedora-cloud-41
|
||||
XFAIL/deleted: opensuse-cloud-tumbleweed debian-cloud-12 debian-cloud-13
|
||||
# Error: unix_fd_server failed. Test 'fd passing; confined -> unconfined' was expected to 'pass'. Reason for failure 'FAIL - bind failed: Permission denied'
|
||||
# Error: unix_fd_server failed. Test 'fd passing; unconfined -> confined' was expected to 'pass'. Reason for failure 'FAIL CLIENT - connect Permission denied'
|
||||
|
Reference in New Issue
Block a user