test: add tests for sandbox

This commit is contained in:
CismonX 2025-03-04 12:05:17 +08:00
parent 182967497d
commit 9050d01fe4
No known key found for this signature in database
GPG key ID: 3094873E29A482FB
6 changed files with 229 additions and 2 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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
View 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
View 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

View file

@ -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])