Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 7 additions & 19 deletions src/libcrun/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -750,34 +750,22 @@ int
libcrun_initialize_apparmor (libcrun_error_t *err)
{
cleanup_close int fd = -1;
int ret;
int size;
char buf[2];

if (apparmor_enabled >= 0)
return apparmor_enabled;

ret = crun_dir_p_at (AT_FDCWD, "/sys/kernel/security/apparmor", true, err);
if (UNLIKELY (ret < 0))
{
/* Directory doesn't exist — not an error, just no AppArmor. */
crun_error_release (err);
apparmor_enabled = 0;
return 0;
}

if (ret == 0)
{
/* Path exists but is not a directory — no AppArmor. */
apparmor_enabled = 0;
return 0;
}

fd = open ("/sys/module/apparmor/parameters/enabled", O_RDONLY | O_CLOEXEC);
if (fd == -1)
{
apparmor_enabled = 0;
return 0;
if (errno == ENOENT)
{
apparmor_enabled = 0;
return 0;
}

return crun_make_error (err, errno, "open `/sys/module/apparmor/parameters/enabled`");
}

size = TEMP_FAILURE_RETRY (read (fd, &buf, 2));
Expand Down
79 changes: 79 additions & 0 deletions tests/test_linux_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,84 @@ def test_process_apparmor_profile():
return -1


def test_process_apparmor_profile_userns():
"""Test AppArmor profile is applied inside a user namespace.

Regression test: when a user namespace with UID/GID mappings is used,
the AppArmor profile must still be applied. A refactoring of
libcrun_initialize_apparmor() caused it to treat a failed stat on
/sys/kernel/security/apparmor (which is expected inside a user
namespace where securityfs is not accessible) as "AppArmor disabled",
silently skipping the profile setup.
"""

if not os.path.exists('/sys/kernel/security/apparmor'):
return (77, "AppArmor not available")

if is_rootless():
return (77, "requires root for user namespace with id mappings")

# Load a test AppArmor profile so we can distinguish "profile applied"
# from "no profile" (both would show 'unconfined' otherwise).
profile_name = "crun-test-userns"
profile_text = """
profile %s flags=(attach_disconnected) {
/** rwlk,
/** ix,
capability,
network,
mount,
umount,
pivot_root,
signal,
ptrace,
unix,
}
""" % profile_name

try:
p = subprocess.run(["apparmor_parser", "--replace"],
input=profile_text.encode(), capture_output=True)
if p.returncode != 0:
return (77, "cannot load test AppArmor profile")
except FileNotFoundError:
return (77, "apparmor_parser not found")

try:
conf = base_config()
add_all_namespaces(conf, userns=True)
conf['process']['args'] = ['/init', 'cat', '/proc/1/attr/current']

conf['process']['apparmorProfile'] = profile_name

conf['linux']['uidMappings'] = [
{'containerID': 0, 'hostID': os.getuid(), 'size': 65536}
]
conf['linux']['gidMappings'] = [
{'containerID': 0, 'hostID': os.getgid(), 'size': 65536}
]

out, _ = run_and_get_output(conf, hide_stderr=False)
if profile_name not in out:
logger.info("test failed: expected '%s' in output, got: %s",
profile_name, out.strip())
return -1
return 0

except subprocess.CalledProcessError as e:
output = e.output.decode('utf-8', errors='ignore') if e.output else ''
if any(x in output.lower() for x in ["apparmor", "mount", "proc", "user", "mapping"]):
return (77, "AppArmor with user namespace not available")
logger.info("test failed: %s", e)
return -1
except Exception as e:
logger.info("test failed: %s", e)
return -1
finally:
subprocess.run(["apparmor_parser", "--remove"],
input=profile_text.encode(), capture_output=True)


def test_process_selinux_label():
"""Test SELinux label."""

Expand Down Expand Up @@ -1753,6 +1831,7 @@ def test_mqueue_mount():
"process-no-new-privileges": test_process_no_new_privileges,
"process-oom-score-adj": test_process_oom_score_adj,
"process-apparmor-profile": test_process_apparmor_profile,
"process-apparmor-profile-userns": test_process_apparmor_profile_userns,
"process-selinux-label": test_process_selinux_label,
"process-umask": test_process_umask,
"mount-label": test_mount_label,
Expand Down
Loading