1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2025 Mark Johnston <markj@FreeBSD.org>
*/
#include <sys/param.h>
#include <sys/jail.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <jail.h>
#include <mntopts.h>
#include <stdio.h>
#include <stdlib.h>
#include <atf-c.h>
static void
mkdir_checked(const char *dir, mode_t mode)
{
int error;
error = mkdir(dir, mode);
ATF_REQUIRE_MSG(error == 0 || errno == EEXIST,
"mkdir %s: %s", dir, strerror(errno));
}
static void __unused
mount_nullfs(const char *dir, const char *target)
{
struct iovec *iov;
char errmsg[1024];
int error, iovlen;
iov = NULL;
iovlen = 0;
build_iovec(&iov, &iovlen, __DECONST(char *, "fstype"),
__DECONST(char *, "nullfs"), (size_t)-1);
build_iovec(&iov, &iovlen, __DECONST(char *, "fspath"),
__DECONST(char *, target), (size_t)-1);
build_iovec(&iov, &iovlen, __DECONST(char *, "from"),
__DECONST(char *, dir), (size_t)-1);
build_iovec(&iov, &iovlen, __DECONST(char *, "errmsg"),
errmsg, sizeof(errmsg));
errmsg[0] = '\0';
error = nmount(iov, iovlen, 0);
ATF_REQUIRE_MSG(error == 0, "nmount: %s",
errmsg[0] != '\0' ? errmsg : strerror(errno));
free_iovec(&iov, &iovlen);
}
ATF_TC_WITH_CLEANUP(jail_root);
ATF_TC_HEAD(jail_root, tc)
{
atf_tc_set_md_var(tc, "require.user", "root");
}
ATF_TC_BODY(jail_root, tc)
{
int error, fd, jid;
mkdir_checked("./root", 0755);
mkdir_checked("./root/a", 0755);
mkdir_checked("./root/b", 0755);
mkdir_checked("./root/a/c", 0755);
jid = jail_setv(JAIL_CREATE | JAIL_ATTACH,
"name", "nullfs_jail_root_test",
"allow.mount", "true",
"allow.mount.nullfs", "true",
"enforce_statfs", "1",
"path", "./root",
"persist", NULL,
NULL);
ATF_REQUIRE_MSG(jid >= 0, "jail_setv: %s", jail_errmsg);
mount_nullfs("/a", "/b");
error = chdir("/b/c");
ATF_REQUIRE(error == 0);
error = rename("/a/c", "/c");
ATF_REQUIRE(error == 0);
/* Descending to the jail root should be ok. */
error = chdir("..");
ATF_REQUIRE(error == 0);
/* Going beyond the root will trigger an error. */
error = chdir("..");
ATF_REQUIRE_ERRNO(ENOENT, error != 0);
fd = open("..", O_RDONLY | O_DIRECTORY);
ATF_REQUIRE_ERRNO(ENOENT, fd < 0);
}
ATF_TC_CLEANUP(jail_root, tc)
{
struct statfs fs;
fsid_t fsid;
int error, jid;
error = statfs("./root/b", &fs);
if (error != 0)
err(1, "statfs ./b");
fsid = fs.f_fsid;
error = statfs("./root", &fs);
if (error != 0)
err(1, "statfs ./root");
if (fsid.val[0] != fs.f_fsid.val[0] ||
fsid.val[1] != fs.f_fsid.val[1]) {
error = unmount("./root/b", 0);
if (error != 0)
err(1, "unmount ./root/b");
}
jid = jail_getid("nullfs_jail_root_test");
if (jid >= 0) {
error = jail_remove(jid);
if (error != 0)
err(1, "jail_remove");
}
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, jail_root);
return (atf_no_error());
}
|