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
3 changes: 3 additions & 0 deletions mk/tests.mk
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ TEST_DIR = tests/unit
TEST_SRCS = $(TEST_DIR)/test-runner.c \
$(TEST_DIR)/test-fd-table.c \
$(TEST_DIR)/test-path.c \
$(TEST_DIR)/test-mount.c \
$(TEST_DIR)/test-cli.c \
$(TEST_DIR)/test-identity.c \
$(TEST_DIR)/test-syscall-nr.c \
Expand All @@ -30,6 +31,8 @@ endif
# Unit tests link only the pure-computation sources (no LKL)
TEST_SUPPORT_SRCS = $(SRC_DIR)/fd-table.c \
$(SRC_DIR)/path.c \
$(SRC_DIR)/mount.c \
$(TEST_DIR)/test-mount-stubs.c \
$(SRC_DIR)/cli.c \
$(SRC_DIR)/identity.c \
$(SRC_DIR)/syscall-nr.c \
Expand Down
100 changes: 94 additions & 6 deletions src/mount.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>

#include "kbox/mount.h"
#include "lkl-wrap.h"
#include "syscall-nr.h"

/* MS_BIND from <linux/mount.h> without pulling the full header. */
#define KBOX_MS_BIND 0x1000

/* asm-generic O_* values for LKL openat (same on x86_64 and aarch64). */
#define LKL_O_CREAT 0100
#define LKL_O_EXCL 0200

/* Bind-mount spec parser. */

int kbox_parse_bind_spec(const char *spec, struct kbox_bind_spec *out)
Expand Down Expand Up @@ -118,7 +124,90 @@ int kbox_apply_recommended_mounts(const struct kbox_sysnrs *s,
return 0;
}

/* Bind mounts. */
/* Bind mounts.
*
* The source is a host path; stat it to determine whether the bind-mount
* target inside LKL should be a directory or a regular file. Anything
* other than a regular file or directory is rejected up front.
*/

/* Verify that the existing inode at target has the expected type. */
static int verify_existing_target(const struct kbox_sysnrs *s,
const char *target,
unsigned int expected_mode_type)
{
struct kbox_lkl_stat lkl_st;
long ret;

ret = kbox_lkl_newfstatat(s, AT_FDCWD_LINUX, target, &lkl_st, 0);
if (ret < 0) {
fprintf(stderr, "stat(%s): %s\n", target, kbox_err_text(ret));
return -1;
}
if ((lkl_st.st_mode & S_IFMT) != expected_mode_type) {
fprintf(stderr,
"bind mount: target %s exists but has wrong type "
"(expected 0%o, got 0%o)\n",
target, expected_mode_type, lkl_st.st_mode & S_IFMT);
return -1;
}
return 0;
}

static int create_bind_target(const struct kbox_sysnrs *s,
const char *source,
const char *target)
{
struct stat st;
long ret;

if (lstat(source, &st) < 0) {
fprintf(stderr, "bind mount: cannot stat source %s: %s\n", source,
strerror(errno));
return -1;
}

if (S_ISLNK(st.st_mode)) {
fprintf(stderr, "bind mount: source %s must not be a symlink\n",
source);
return -1;
}

if (S_ISDIR(st.st_mode)) {
ret = kbox_lkl_mkdir(s, target, 0755);
if (ret == -EEXIST)
return verify_existing_target(s, target, S_IFDIR);
if (ret < 0) {
fprintf(stderr, "mkdir(%s): %s\n", target, kbox_err_text(ret));
return -1;
}
return 0;
}

if (S_ISREG(st.st_mode)) {
long cret;

ret = kbox_lkl_openat(s, AT_FDCWD_LINUX, target,
LKL_O_CREAT | LKL_O_EXCL, 0644);
if (ret == -EEXIST)
return verify_existing_target(s, target, S_IFREG);
if (ret < 0) {
fprintf(stderr, "creat(%s): %s\n", target, kbox_err_text(ret));
return -1;
}
cret = kbox_lkl_close(s, ret);
if (cret < 0) {
fprintf(stderr, "close(%s): %s\n", target, kbox_err_text(cret));
return -1;
}
return 0;
}

fprintf(stderr,
"bind mount: source %s is neither a regular file nor a directory\n",
source);
return -1;
}

int kbox_apply_bind_mounts(const struct kbox_sysnrs *s,
const struct kbox_bind_spec *specs,
Expand All @@ -127,13 +216,12 @@ int kbox_apply_bind_mounts(const struct kbox_sysnrs *s,
int i;
long ret;

if (!s || (!specs && count > 0) || count < 0)
return -1;

for (i = 0; i < count; i++) {
ret = kbox_lkl_mkdir(s, specs[i].target, 0755);
if (ret < 0 && ret != -EEXIST) {
fprintf(stderr, "mkdir(%s): %s\n", specs[i].target,
kbox_err_text(ret));
if (create_bind_target(s, specs[i].source, specs[i].target) < 0)
return -1;
}

ret = kbox_lkl_mount(s, specs[i].source, specs[i].target, NULL,
KBOX_MS_BIND, NULL);
Expand Down
74 changes: 74 additions & 0 deletions tests/unit/test-mount-stubs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* SPDX-License-Identifier: MIT */
/* Stubs for LKL functions referenced by mount.c but not exercised
* in the unit tests (only kbox_parse_bind_spec is tested).
*/

#include <errno.h>

#include "lkl-wrap.h"
#include "syscall-nr.h"

long kbox_lkl_mkdir(const struct kbox_sysnrs *s, const char *path, int mode)
{
(void) s;
(void) path;
(void) mode;
return -ENOSYS;
}

long kbox_lkl_mount(const struct kbox_sysnrs *s,
const char *src,
const char *target,
const char *fstype,
long flags,
const void *data)
{
(void) s;
(void) src;
(void) target;
(void) fstype;
(void) flags;
(void) data;
return -ENOSYS;
}

long kbox_lkl_openat(const struct kbox_sysnrs *s,
long dirfd,
const char *path,
long flags,
long mode)
{
(void) s;
(void) dirfd;
(void) path;
(void) flags;
(void) mode;
return -ENOSYS;
}

long kbox_lkl_close(const struct kbox_sysnrs *s, long fd)
{
(void) s;
(void) fd;
return 0;
}

long kbox_lkl_newfstatat(const struct kbox_sysnrs *s,
long dirfd,
const char *path,
void *buf,
long flags)
{
(void) s;
(void) dirfd;
(void) path;
(void) buf;
(void) flags;
return -ENOSYS;
}

const char *kbox_err_text(long code)
{
(void) code;
return "stub";
}
103 changes: 103 additions & 0 deletions tests/unit/test-mount.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* SPDX-License-Identifier: MIT */
#include <string.h>

#include "kbox/mount.h"
#include "test-runner.h"

static void fill_char(char *buf, size_t len, char c)
{
memset(buf, c, len);
buf[len] = '\0';
}

/* --- kbox_parse_bind_spec tests --- */

static void test_parse_basic(void)
{
struct kbox_bind_spec spec;
ASSERT_EQ(kbox_parse_bind_spec("/host/dir:/guest/dir", &spec), 0);
ASSERT_STREQ(spec.source, "/host/dir");
ASSERT_STREQ(spec.target, "/guest/dir");
}

static void test_parse_null_spec(void)
{
struct kbox_bind_spec spec;
ASSERT_EQ(kbox_parse_bind_spec(NULL, &spec), -1);
}

static void test_parse_null_out(void)
{
ASSERT_EQ(kbox_parse_bind_spec("/a:/b", NULL), -1);
}

static void test_parse_no_colon(void)
{
struct kbox_bind_spec spec;
ASSERT_EQ(kbox_parse_bind_spec("/no/colon/here", &spec), -1);
}

static void test_parse_empty_source(void)
{
struct kbox_bind_spec spec;
ASSERT_EQ(kbox_parse_bind_spec(":/guest", &spec), -1);
}

static void test_parse_empty_target(void)
{
struct kbox_bind_spec spec;
ASSERT_EQ(kbox_parse_bind_spec("/host:", &spec), -1);
}

static void test_parse_colon_in_middle(void)
{
struct kbox_bind_spec spec;
ASSERT_EQ(kbox_parse_bind_spec("a:b", &spec), 0);
ASSERT_STREQ(spec.source, "a");
ASSERT_STREQ(spec.target, "b");
}

static void test_parse_multiple_colons(void)
{
struct kbox_bind_spec spec;
ASSERT_EQ(kbox_parse_bind_spec("/a:/b:c", &spec), 0);
ASSERT_STREQ(spec.source, "/a");
ASSERT_STREQ(spec.target, "/b:c");
}

static void test_parse_source_too_long(void)
{
struct kbox_bind_spec spec;
/* source component >= sizeof(spec.source) must be rejected */
char big[4096 + 1 + 2]; /* 4096 'x' + ':' + '/' + NUL */
fill_char(big, 4096, 'x');
big[4096] = ':';
big[4097] = '/';
big[4098] = '\0';
ASSERT_EQ(kbox_parse_bind_spec(big, &spec), -1);
}

static void test_parse_target_too_long(void)
{
struct kbox_bind_spec spec;
/* target component >= sizeof(spec.target) must be rejected */
char big[2 + 4096 + 1]; /* '/' + ':' + 4096 'y' + NUL */
big[0] = '/';
big[1] = ':';
fill_char(big + 2, 4096, 'y');
ASSERT_EQ(kbox_parse_bind_spec(big, &spec), -1);
}

void test_mount_init(void)
{
TEST_REGISTER(test_parse_basic);
TEST_REGISTER(test_parse_null_spec);
TEST_REGISTER(test_parse_null_out);
TEST_REGISTER(test_parse_no_colon);
TEST_REGISTER(test_parse_empty_source);
TEST_REGISTER(test_parse_empty_target);
TEST_REGISTER(test_parse_colon_in_middle);
TEST_REGISTER(test_parse_multiple_colons);
TEST_REGISTER(test_parse_source_too_long);
TEST_REGISTER(test_parse_target_too_long);
}
2 changes: 2 additions & 0 deletions tests/unit/test-runner.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ void test_pass(void)
/* Portable test suites (all hosts) */
extern void test_fd_table_init(void);
extern void test_path_init(void);
extern void test_mount_init(void);
extern void test_cli_init(void);
extern void test_identity_init(void);
extern void test_syscall_nr_init(void);
Expand Down Expand Up @@ -120,6 +121,7 @@ int main(int argc, char *argv[])
/* Portable suites */
test_fd_table_init();
test_path_init();
test_mount_init();
test_cli_init();
test_identity_init();
test_syscall_nr_init();
Expand Down
Loading