diff --git a/.gitignore b/.gitignore index 57430c5e2..a32c3ab49 100644 --- a/.gitignore +++ b/.gitignore @@ -255,6 +255,7 @@ tests/regression/apparmor/introspect tests/regression/apparmor/io_uring tests/regression/apparmor/link tests/regression/apparmor/link_subset +tests/regression/apparmor/linkat_tmpfile tests/regression/apparmor/mkdir tests/regression/apparmor/mmap tests/regression/apparmor/mount diff --git a/tests/regression/apparmor/Makefile b/tests/regression/apparmor/Makefile index 643e42855..1f4bc00b6 100644 --- a/tests/regression/apparmor/Makefile +++ b/tests/regression/apparmor/Makefile @@ -144,6 +144,7 @@ SRC=access.c \ getcon_verify.c \ link.c \ link_subset.c \ + linkat_tmpfile.c \ mmap.c \ mkdir.c \ mount.c \ @@ -311,6 +312,7 @@ TESTS=aa_exec \ i18n \ link \ link_subset \ + linkat_tmpfile \ mkdir \ mmap \ mount \ diff --git a/tests/regression/apparmor/linkat_tmpfile.c b/tests/regression/apparmor/linkat_tmpfile.c new file mode 100644 index 000000000..5617f93fc --- /dev/null +++ b/tests/regression/apparmor/linkat_tmpfile.c @@ -0,0 +1,29 @@ +#define _GNU_SOURCE + +#include +#include + +#include + +int main(int argc, char **argv) { + if (argc != 2 && argc != 3) { + fprintf(stderr, "FAIL: Usage: linkat tmpdir final_location\n"); + return 1; + } + int tmpfile_fd = open(argv[1], O_TMPFILE | O_WRONLY, S_IRUSR | S_IWUSR); + if (tmpfile_fd == -1) { + perror("FAIL: could not open tmpfile"); + return 1; + } + if (argc == 3) { + int linkat_result = linkat(tmpfile_fd, "", AT_FDCWD, argv[2], AT_EMPTY_PATH); + if (linkat_result == -1) { + perror("FAIL: could not link tmpfile into final location"); + close(tmpfile_fd); + return 1; + } + } + close(tmpfile_fd); + fprintf(stderr, "PASS\n"); + return 0; +} diff --git a/tests/regression/apparmor/linkat_tmpfile.sh b/tests/regression/apparmor/linkat_tmpfile.sh new file mode 100644 index 000000000..16f4bcedd --- /dev/null +++ b/tests/regression/apparmor/linkat_tmpfile.sh @@ -0,0 +1,52 @@ +#! /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 linkat +#=DESCRIPTION +# Verifies that file creation with O_TMPFILE and linkat(2) is mediated correctly +#=END + +pwd=`dirname $0` +pwd=`cd $pwd ; /bin/pwd` + +bin=$pwd + +. "$bin/prologue.inc" + +tmpdir_nested=$tmpdir/nested +tmpdir_nested_file=$tmpdir_nested/file +tmpfile=$tmpdir/file + +mkdir $tmpdir_nested + +genprofile cap:dac_read_search +runchecktest "linkat O_TMPFILE noperms" fail $tmpdir_nested +runchecktest "linkat O_TMPFILE noperms, link" fail $tmpdir_nested $tmpfile + +# Denial log entry for tmpfile is /path/#[6digits] +# Don't assume because O_TMPFILE fds should lack a name entirely +genprofile cap:dac_read_search "${tmpdir_nested}/:w" "${tmpdir_nested}/*:w" +runchecktest "linkat O_TMPFILE tmpdir only" pass $tmpdir_nested +runchecktest "linkat O_TMPFILE tmpdir only, link" fail $tmpdir_nested $tmpfile + +genprofile cap:dac_read_search "${tmpfile}:w" +runchecktest "linkat O_TMPFILE tmpfile only" fail $tmpdir_nested +runchecktest "linkat O_TMPFILE tmpfile only, link" fail $tmpdir_nested $tmpfile + +genprofile cap:dac_read_search "${tmpdir_nested}/:w" "${tmpdir_nested}/*:w" "${tmpfile}:w" +runchecktest "linkat O_TMPFILE tmpdir and tmpfile (w)" pass $tmpdir_nested +# Even if semantically a (w)rite it gets logged as the (l)ink that it actually is +runchecktest "linkat O_TMPFILE tmpdir and tmpfile (w), link" xpass $tmpdir_nested $tmpfile + +genprofile cap:dac_read_search "${tmpdir_nested}/:w" "${tmpdir_nested}/*:w" "${tmpfile}:l" +runchecktest "linkat O_TMPFILE tmpdir and tmpfile (l)" pass $tmpdir_nested +# Even if semantically a (w)rite we want to test backwards compatibility with (l)ink as it is currently seen +runchecktest "linkat O_TMPFILE tmpdir and tmpfile (l), link" pass $tmpdir_nested $tmpfile + +rm $tmpfile +rmdir $tmpdir_nested \ No newline at end of file