Skip to content

Commit 0000e1e

Browse files
committed
Support file bind mounts in addition to directories
kbox_apply_bind_mounts() unconditionally mkdir'd target before MS_BIND, silently failing for file sources. Stat the host source to determine its type: create a directory target via mkdir for directories, or an empty file target via openat(O_CREAT|O_EXCL) for regular files. Reject symlinks, devices, and other non-regular types up front. On EEXIST, verify the existing target matches the expected type to catch mismatches early instead of deferring to a confusing mount error. Close #39 Change-Id: I287c480fbe80301911e0b64702db2b6abb48a70f
1 parent fa29f7a commit 0000e1e

5 files changed

Lines changed: 270 additions & 6 deletions

File tree

mk/tests.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ TEST_DIR = tests/unit
66
TEST_SRCS = $(TEST_DIR)/test-runner.c \
77
$(TEST_DIR)/test-fd-table.c \
88
$(TEST_DIR)/test-path.c \
9+
$(TEST_DIR)/test-mount.c \
910
$(TEST_DIR)/test-cli.c \
1011
$(TEST_DIR)/test-identity.c \
1112
$(TEST_DIR)/test-syscall-nr.c \
@@ -30,6 +31,8 @@ endif
3031
# Unit tests link only the pure-computation sources (no LKL)
3132
TEST_SUPPORT_SRCS = $(SRC_DIR)/fd-table.c \
3233
$(SRC_DIR)/path.c \
34+
$(SRC_DIR)/mount.c \
35+
$(TEST_DIR)/test-mount-stubs.c \
3336
$(SRC_DIR)/cli.c \
3437
$(SRC_DIR)/identity.c \
3538
$(SRC_DIR)/syscall-nr.c \

src/mount.c

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@
1010
#include <errno.h>
1111
#include <stdio.h>
1212
#include <string.h>
13+
#include <sys/stat.h>
1314

1415
#include "kbox/mount.h"
1516
#include "lkl-wrap.h"
17+
#include "syscall-nr.h"
1618

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

22+
/* asm-generic O_* values for LKL openat (same on x86_64 and aarch64). */
23+
#define LKL_O_CREAT 0100
24+
#define LKL_O_EXCL 0200
25+
2026
/* Bind-mount spec parser. */
2127

2228
int kbox_parse_bind_spec(const char *spec, struct kbox_bind_spec *out)
@@ -118,7 +124,84 @@ int kbox_apply_recommended_mounts(const struct kbox_sysnrs *s,
118124
return 0;
119125
}
120126

121-
/* Bind mounts. */
127+
/* Bind mounts.
128+
*
129+
* The source is a host path; stat it to determine whether the bind-mount
130+
* target inside LKL should be a directory or a regular file. Anything
131+
* other than a regular file or directory is rejected up front.
132+
*/
133+
134+
/* Verify that the existing inode at target has the expected type. */
135+
static int verify_existing_target(const struct kbox_sysnrs *s,
136+
const char *target,
137+
unsigned int expected_mode_type)
138+
{
139+
struct kbox_lkl_stat lkl_st;
140+
long ret;
141+
142+
ret = kbox_lkl_newfstatat(s, AT_FDCWD_LINUX, target, &lkl_st, 0);
143+
if (ret < 0) {
144+
fprintf(stderr, "stat(%s): %s\n", target, kbox_err_text(ret));
145+
return -1;
146+
}
147+
if ((lkl_st.st_mode & S_IFMT) != expected_mode_type) {
148+
fprintf(stderr,
149+
"bind mount: target %s exists but has wrong type "
150+
"(expected 0%o, got 0%o)\n",
151+
target, expected_mode_type, lkl_st.st_mode & S_IFMT);
152+
return -1;
153+
}
154+
return 0;
155+
}
156+
157+
static int create_bind_target(const struct kbox_sysnrs *s,
158+
const char *source,
159+
const char *target)
160+
{
161+
struct stat st;
162+
long ret;
163+
164+
if (stat(source, &st) < 0) {
165+
fprintf(stderr, "bind mount: cannot stat source %s: %s\n", source,
166+
strerror(errno));
167+
return -1;
168+
}
169+
170+
if (S_ISDIR(st.st_mode)) {
171+
ret = kbox_lkl_mkdir(s, target, 0755);
172+
if (ret == -EEXIST)
173+
return verify_existing_target(s, target, S_IFDIR);
174+
if (ret < 0) {
175+
fprintf(stderr, "mkdir(%s): %s\n", target, kbox_err_text(ret));
176+
return -1;
177+
}
178+
return 0;
179+
}
180+
181+
if (S_ISREG(st.st_mode)) {
182+
long cret;
183+
184+
ret = kbox_lkl_openat(s, AT_FDCWD_LINUX, target,
185+
LKL_O_CREAT | LKL_O_EXCL, 0644);
186+
if (ret == -EEXIST)
187+
return verify_existing_target(s, target, S_IFREG);
188+
if (ret < 0) {
189+
fprintf(stderr, "creat(%s): %s\n", target, kbox_err_text(ret));
190+
return -1;
191+
}
192+
cret = kbox_lkl_close(s, ret);
193+
if (cret < 0) {
194+
fprintf(stderr, "close(%s): %s\n", target, kbox_err_text(cret));
195+
return -1;
196+
}
197+
return 0;
198+
}
199+
200+
fprintf(stderr,
201+
"bind mount: source %s is neither a regular file nor a directory\n",
202+
source);
203+
return -1;
204+
}
122205

123206
int kbox_apply_bind_mounts(const struct kbox_sysnrs *s,
124207
const struct kbox_bind_spec *specs,
@@ -127,13 +210,12 @@ int kbox_apply_bind_mounts(const struct kbox_sysnrs *s,
127210
int i;
128211
long ret;
129212

213+
if (!s || (!specs && count > 0) || count < 0)
214+
return -1;
215+
130216
for (i = 0; i < count; i++) {
131-
ret = kbox_lkl_mkdir(s, specs[i].target, 0755);
132-
if (ret < 0 && ret != -EEXIST) {
133-
fprintf(stderr, "mkdir(%s): %s\n", specs[i].target,
134-
kbox_err_text(ret));
217+
if (create_bind_target(s, specs[i].source, specs[i].target) < 0)
135218
return -1;
136-
}
137219

138220
ret = kbox_lkl_mount(s, specs[i].source, specs[i].target, NULL,
139221
KBOX_MS_BIND, NULL);

tests/unit/test-mount-stubs.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/* SPDX-License-Identifier: MIT */
2+
/* Stubs for LKL functions referenced by mount.c but not exercised
3+
* in the unit tests (only kbox_parse_bind_spec is tested).
4+
*/
5+
6+
#include <errno.h>
7+
8+
#include "lkl-wrap.h"
9+
#include "syscall-nr.h"
10+
11+
long kbox_lkl_mkdir(const struct kbox_sysnrs *s, const char *path, int mode)
12+
{
13+
(void) s;
14+
(void) path;
15+
(void) mode;
16+
return -ENOSYS;
17+
}
18+
19+
long kbox_lkl_mount(const struct kbox_sysnrs *s,
20+
const char *src,
21+
const char *target,
22+
const char *fstype,
23+
long flags,
24+
const void *data)
25+
{
26+
(void) s;
27+
(void) src;
28+
(void) target;
29+
(void) fstype;
30+
(void) flags;
31+
(void) data;
32+
return -ENOSYS;
33+
}
34+
35+
long kbox_lkl_openat(const struct kbox_sysnrs *s,
36+
long dirfd,
37+
const char *path,
38+
long flags,
39+
long mode)
40+
{
41+
(void) s;
42+
(void) dirfd;
43+
(void) path;
44+
(void) flags;
45+
(void) mode;
46+
return -ENOSYS;
47+
}
48+
49+
long kbox_lkl_close(const struct kbox_sysnrs *s, long fd)
50+
{
51+
(void) s;
52+
(void) fd;
53+
return 0;
54+
}
55+
56+
long kbox_lkl_newfstatat(const struct kbox_sysnrs *s,
57+
long dirfd,
58+
const char *path,
59+
void *buf,
60+
long flags)
61+
{
62+
(void) s;
63+
(void) dirfd;
64+
(void) path;
65+
(void) buf;
66+
(void) flags;
67+
return -ENOSYS;
68+
}
69+
70+
const char *kbox_err_text(long code)
71+
{
72+
(void) code;
73+
return "stub";
74+
}

tests/unit/test-mount.c

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/* SPDX-License-Identifier: MIT */
2+
#include <string.h>
3+
4+
#include "kbox/mount.h"
5+
#include "test-runner.h"
6+
7+
static void fill_char(char *buf, size_t len, char c)
8+
{
9+
memset(buf, c, len);
10+
buf[len] = '\0';
11+
}
12+
13+
/* --- kbox_parse_bind_spec tests --- */
14+
15+
static void test_parse_basic(void)
16+
{
17+
struct kbox_bind_spec spec;
18+
ASSERT_EQ(kbox_parse_bind_spec("/host/dir:/guest/dir", &spec), 0);
19+
ASSERT_STREQ(spec.source, "/host/dir");
20+
ASSERT_STREQ(spec.target, "/guest/dir");
21+
}
22+
23+
static void test_parse_null_spec(void)
24+
{
25+
struct kbox_bind_spec spec;
26+
ASSERT_EQ(kbox_parse_bind_spec(NULL, &spec), -1);
27+
}
28+
29+
static void test_parse_null_out(void)
30+
{
31+
ASSERT_EQ(kbox_parse_bind_spec("/a:/b", NULL), -1);
32+
}
33+
34+
static void test_parse_no_colon(void)
35+
{
36+
struct kbox_bind_spec spec;
37+
ASSERT_EQ(kbox_parse_bind_spec("/no/colon/here", &spec), -1);
38+
}
39+
40+
static void test_parse_empty_source(void)
41+
{
42+
struct kbox_bind_spec spec;
43+
ASSERT_EQ(kbox_parse_bind_spec(":/guest", &spec), -1);
44+
}
45+
46+
static void test_parse_empty_target(void)
47+
{
48+
struct kbox_bind_spec spec;
49+
ASSERT_EQ(kbox_parse_bind_spec("/host:", &spec), -1);
50+
}
51+
52+
static void test_parse_colon_in_middle(void)
53+
{
54+
struct kbox_bind_spec spec;
55+
ASSERT_EQ(kbox_parse_bind_spec("a:b", &spec), 0);
56+
ASSERT_STREQ(spec.source, "a");
57+
ASSERT_STREQ(spec.target, "b");
58+
}
59+
60+
static void test_parse_multiple_colons(void)
61+
{
62+
struct kbox_bind_spec spec;
63+
ASSERT_EQ(kbox_parse_bind_spec("/a:/b:c", &spec), 0);
64+
ASSERT_STREQ(spec.source, "/a");
65+
ASSERT_STREQ(spec.target, "/b:c");
66+
}
67+
68+
static void test_parse_source_too_long(void)
69+
{
70+
struct kbox_bind_spec spec;
71+
/* source component >= sizeof(spec.source) must be rejected */
72+
char big[4096 + 1 + 2]; /* 4096 'x' + ':' + '/' + NUL */
73+
fill_char(big, 4096, 'x');
74+
big[4096] = ':';
75+
big[4097] = '/';
76+
big[4098] = '\0';
77+
ASSERT_EQ(kbox_parse_bind_spec(big, &spec), -1);
78+
}
79+
80+
static void test_parse_target_too_long(void)
81+
{
82+
struct kbox_bind_spec spec;
83+
/* target component >= sizeof(spec.target) must be rejected */
84+
char big[2 + 4096 + 1]; /* '/' + ':' + 4096 'y' + NUL */
85+
big[0] = '/';
86+
big[1] = ':';
87+
fill_char(big + 2, 4096, 'y');
88+
ASSERT_EQ(kbox_parse_bind_spec(big, &spec), -1);
89+
}
90+
91+
void test_mount_init(void)
92+
{
93+
TEST_REGISTER(test_parse_basic);
94+
TEST_REGISTER(test_parse_null_spec);
95+
TEST_REGISTER(test_parse_null_out);
96+
TEST_REGISTER(test_parse_no_colon);
97+
TEST_REGISTER(test_parse_empty_source);
98+
TEST_REGISTER(test_parse_empty_target);
99+
TEST_REGISTER(test_parse_colon_in_middle);
100+
TEST_REGISTER(test_parse_multiple_colons);
101+
TEST_REGISTER(test_parse_source_too_long);
102+
TEST_REGISTER(test_parse_target_too_long);
103+
}

tests/unit/test-runner.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ void test_pass(void)
9191
/* Portable test suites (all hosts) */
9292
extern void test_fd_table_init(void);
9393
extern void test_path_init(void);
94+
extern void test_mount_init(void);
9495
extern void test_cli_init(void);
9596
extern void test_identity_init(void);
9697
extern void test_syscall_nr_init(void);
@@ -120,6 +121,7 @@ int main(int argc, char *argv[])
120121
/* Portable suites */
121122
test_fd_table_init();
122123
test_path_init();
124+
test_mount_init();
123125
test_cli_init();
124126
test_identity_init();
125127
test_syscall_nr_init();

0 commit comments

Comments
 (0)