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_)
|
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
|
# Helper programs for testing
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ if BOOKMARKFS_UTIL
|
||||||
|
|
||||||
check_util_lib_CPPFLAGS = -I$(top_srcdir)/src
|
check_util_lib_CPPFLAGS = -I$(top_srcdir)/src
|
||||||
check_util_lib_LDADD = $(top_builddir)/src/libbookmarkfs_util.la
|
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
|
endif # BOOKMARKFS_UTIL
|
||||||
|
|
||||||
# Autotest setup
|
# Autotest setup
|
||||||
|
|
|
@ -59,6 +59,8 @@ dispatch_subcmds (
|
||||||
status = subcmd_prng(argc, argv);
|
status = subcmd_prng(argc, argv);
|
||||||
} else if (0 == strcmp("watcher", cmd)) {
|
} else if (0 == strcmp("watcher", cmd)) {
|
||||||
status = check_watcher(argc, argv);
|
status = check_watcher(argc, argv);
|
||||||
|
} else if (0 == strcmp("sandbox", cmd)) {
|
||||||
|
status = check_sandbox(argc, argv);
|
||||||
} else {
|
} else {
|
||||||
log_printf("bad subcmd '%s'", cmd);
|
log_printf("bad subcmd '%s'", cmd);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,12 @@
|
||||||
action_if_false \
|
action_if_false \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
int
|
||||||
|
check_sandbox (
|
||||||
|
int argc,
|
||||||
|
char *argv[]
|
||||||
|
);
|
||||||
|
|
||||||
int
|
int
|
||||||
check_watcher (
|
check_watcher (
|
||||||
int argc,
|
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_hash.at])
|
||||||
m4_include([lib_prng.at])
|
m4_include([lib_prng.at])
|
||||||
m4_include([lib_watcher.at])
|
m4_include([lib_watcher.at])
|
||||||
|
m4_include([lib_sandbox.at])
|
||||||
|
|
Loading…
Add table
Reference in a new issue