mirror of
https://git.sr.ht/~cismonx/bookmarkfs
synced 2025-06-07 19:58:50 +00:00
test: add tests for sandbox
This commit is contained in:
parent
182967497d
commit
9050d01fe4
6 changed files with 229 additions and 2 deletions
|
@ -8,7 +8,7 @@
|
|||
|
||||
EXTRA_DIST = package.m4 testsuite.at $(TESTSUITE) $(TESTS_)
|
||||
|
||||
TESTS_ = lib_hash.at lib_prng.at lib_watcher.at
|
||||
TESTS_ = lib_hash.at lib_prng.at lib_watcher.at lib_sandbox.at
|
||||
|
||||
# Helper programs for testing
|
||||
|
||||
|
@ -20,7 +20,7 @@ if BOOKMARKFS_UTIL
|
|||
|
||||
check_util_lib_CPPFLAGS = -I$(top_srcdir)/src
|
||||
check_util_lib_LDADD = $(top_builddir)/src/libbookmarkfs_util.la
|
||||
check_util_lib_SOURCES = check_lib.c check_watcher.c
|
||||
check_util_lib_SOURCES = check_lib.c check_watcher.c check_sandbox.c
|
||||
endif # BOOKMARKFS_UTIL
|
||||
|
||||
# Autotest setup
|
||||
|
|
|
@ -59,6 +59,8 @@ dispatch_subcmds (
|
|||
status = subcmd_prng(argc, argv);
|
||||
} else if (0 == strcmp("watcher", cmd)) {
|
||||
status = check_watcher(argc, argv);
|
||||
} else if (0 == strcmp("sandbox", cmd)) {
|
||||
status = check_sandbox(argc, argv);
|
||||
} else {
|
||||
log_printf("bad subcmd '%s'", cmd);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,12 @@
|
|||
action_if_false \
|
||||
} while (0)
|
||||
|
||||
int
|
||||
check_sandbox (
|
||||
int argc,
|
||||
char *argv[]
|
||||
);
|
||||
|
||||
int
|
||||
check_watcher (
|
||||
int argc,
|
||||
|
|
193
tests/check_sandbox.c
Normal file
193
tests/check_sandbox.c
Normal file
|
@ -0,0 +1,193 @@
|
|||
/**
|
||||
* bookmarkfs/tests/check_sandbox.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2025 CismonX <admin@cismon.net>
|
||||
*
|
||||
* 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "check_lib.h"
|
||||
#include "frontend_util.h"
|
||||
#include "sandbox.h"
|
||||
|
||||
// Forward declaration start
|
||||
static int do_check_sandbox (int, uint32_t);
|
||||
// Forward declaration end
|
||||
|
||||
static int
|
||||
do_check_sandbox (
|
||||
int dirfd,
|
||||
uint32_t flags
|
||||
) {
|
||||
#define FILE1_NAME "false.sh"
|
||||
#define FILE2_NAME "foo.tmp"
|
||||
|
||||
#if defined(__linux__)
|
||||
# define ERR1 EACCES
|
||||
# define ERR2 EPERM
|
||||
#elif defined(__FreeBSD__)
|
||||
# define ERR1 ENOTCAPABLE
|
||||
# define ERR2 ECAPMODE
|
||||
#else
|
||||
# error "not implemented"
|
||||
#endif
|
||||
|
||||
#define ASSERT_BAD_SYS(expr, cleanup_action) \
|
||||
ASSERT_EXPR_INT(expr, r_, (err_ = errno, r_ < 0), { \
|
||||
cleanup_action \
|
||||
goto end; \
|
||||
}); \
|
||||
ASSERT_EXPR_INT(err_, r_, r_ == ERR1 || r_ == ERR2, goto end;)
|
||||
|
||||
#define ASSERT_BAD_FD(expr) ASSERT_BAD_SYS(expr, close(r_);)
|
||||
#define ASSERT_EQ(val, expr) ASSERT_EXPR_INT(expr, r_, (val) == r_, goto end;)
|
||||
#define ASSERT_NE(val, expr) ASSERT_EXPR_INT(expr, r_, (val) != r_, goto end;)
|
||||
|
||||
int err_;
|
||||
int status = -1;
|
||||
|
||||
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
#if defined(__linux__)
|
||||
ASSERT_EQ(-1, fd);
|
||||
#elif defined(__FreeBSD__)
|
||||
// In capability mode, socket() is allowed,
|
||||
// but bind(), connect(), etc., are not.
|
||||
ASSERT_NE(-1, fd);
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
};
|
||||
ASSERT_BAD_SYS(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), );
|
||||
close(fd);
|
||||
#else
|
||||
# error "not implemented"
|
||||
#endif
|
||||
fd = -1;
|
||||
|
||||
// Not allowed to perform filesystem lookups with AT_FDCWD.
|
||||
ASSERT_BAD_FD(openat(AT_FDCWD, ".", O_RDONLY));
|
||||
struct stat buf;
|
||||
ASSERT_BAD_SYS(fstatat(AT_FDCWD, ".", &buf, 0), );
|
||||
|
||||
bool check_above = true;
|
||||
bool check_lookup_above = true;
|
||||
#ifdef __linux__
|
||||
# ifndef BOOKMARKFS_SANDBOX_LANDLOCK
|
||||
// If only we could filter renameat2() with seccomp...
|
||||
check_above = false;
|
||||
# endif
|
||||
// LANDLOCK_RULE_PATH_BENEATH does not prohibit lookup
|
||||
// above the given directory.
|
||||
// Unfiltered syscalls (e.g., fstatat()) are still able to
|
||||
// operate on the entire filesystem tree.
|
||||
// This is not desired, but generally harmless.
|
||||
check_lookup_above = false;
|
||||
#endif
|
||||
if (check_above) {
|
||||
// Not allowed to operate above the directory.
|
||||
ASSERT_BAD_FD(openat(dirfd, "..", O_RDONLY));
|
||||
if (check_lookup_above) {
|
||||
ASSERT_BAD_SYS(fstatat(dirfd, "..", &buf, 0), );
|
||||
}
|
||||
}
|
||||
|
||||
fd = openat(dirfd, FILE1_NAME, O_RDONLY);
|
||||
ASSERT_NE(-1, fd);
|
||||
|
||||
// Not allowed to execute files.
|
||||
char *argv[] = { FILE2_NAME, NULL };
|
||||
char *envp[] = { NULL };
|
||||
ASSERT_BAD_SYS(fexecve(fd, argv, envp), );
|
||||
|
||||
if (flags & SANDBOX_READONLY) {
|
||||
// Not allowed to create, modify or delete files in read-only mode.
|
||||
ASSERT_BAD_FD(openat(dirfd, FILE2_NAME, O_RDONLY | O_CREAT, 0600));
|
||||
ASSERT_BAD_SYS(renameat(dirfd, FILE1_NAME, dirfd, FILE2_NAME), );
|
||||
ASSERT_BAD_SYS(unlinkat(dirfd, FILE1_NAME, 0), );
|
||||
} else {
|
||||
close(fd);
|
||||
fd = openat(dirfd, FILE2_NAME, O_RDONLY | O_CREAT, 0600);
|
||||
ASSERT_NE(-1, fd);
|
||||
|
||||
ASSERT_EQ(0, renameat(dirfd, FILE1_NAME, dirfd, FILE2_NAME));
|
||||
ASSERT_EQ(0, unlinkat(dirfd, FILE2_NAME, 0));
|
||||
}
|
||||
|
||||
status = 0;
|
||||
|
||||
end:
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
int
|
||||
check_sandbox (
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
uint32_t flags = 0;
|
||||
#ifndef BOOKMARKFS_SANDBOX_LANDLOCK
|
||||
flags |= SANDBOX_NO_LANDLOCK;
|
||||
#endif
|
||||
char const *path = NULL;
|
||||
|
||||
getopt_foreach(argc, argv, ":d:r") {
|
||||
case 'd':
|
||||
path = optarg;
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
flags |= SANDBOX_READONLY;
|
||||
break;
|
||||
|
||||
default:
|
||||
log_printf("bad option '-%c'", optopt);
|
||||
return -1;
|
||||
}
|
||||
if (path == NULL) {
|
||||
log_puts("path not specified");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int dirfd = open(path, O_RDONLY | O_DIRECTORY);
|
||||
if (dirfd < 0) {
|
||||
log_printf("failed to open '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
int status = sandbox_enter(dirfd, flags);
|
||||
if (status < 0) {
|
||||
goto end;
|
||||
}
|
||||
status = do_check_sandbox(dirfd, flags);
|
||||
|
||||
end:
|
||||
close(dirfd);
|
||||
return status;
|
||||
}
|
25
tests/lib_sandbox.at
Normal file
25
tests/lib_sandbox.at
Normal file
|
@ -0,0 +1,25 @@
|
|||
dnl
|
||||
dnl Copyright (C) 2025 CismonX <admin@cismon.net>
|
||||
dnl
|
||||
dnl Copying and distribution of this file, with or without modification,
|
||||
dnl are permitted in any medium without royalty,
|
||||
dnl provided the copyright notice and this notice are preserved.
|
||||
dnl This file is offered as-is, without any warranty.
|
||||
dnl
|
||||
|
||||
AT_SETUP([util lib: sandbox])
|
||||
AT_KEYWORDS([lib sandbox])
|
||||
|
||||
ATX_CHECK_LIB([
|
||||
tmpdir=./$(ath_fn_rand_u64_hex).tmp.d
|
||||
|
||||
mkdir $tmpdir
|
||||
printf '%s\n\n%s\n' '#!/bin/sh' 'exit 2' > $tmpdir/false.sh
|
||||
chmod +x $tmpdir/false.sh
|
||||
|
||||
# Check both read-only and read/write mode.
|
||||
check-util-lib sandbox -r -d $tmpdir
|
||||
check-util-lib sandbox -d $tmpdir
|
||||
])
|
||||
|
||||
AT_CLEANUP
|
|
@ -75,3 +75,4 @@ AT_BANNER([The Utility Library])
|
|||
m4_include([lib_hash.at])
|
||||
m4_include([lib_prng.at])
|
||||
m4_include([lib_watcher.at])
|
||||
m4_include([lib_sandbox.at])
|
||||
|
|
Loading…
Add table
Reference in a new issue