1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright (c) 2019 Facebook
3 
4 #include <fcntl.h>
5 #include <stdint.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <unistd.h>
10 
11 #include <linux/filter.h>
12 
13 #include <bpf/bpf.h>
14 
15 #include "bpf_rlimit.h"
16 #include "bpf_util.h"
17 #include "cgroup_helpers.h"
18 
19 #define CG_PATH			"/foo"
20 #define MAX_INSNS		512
21 
22 char bpf_log_buf[BPF_LOG_BUF_SIZE];
23 
24 struct sysctl_test {
25 	const char *descr;
26 	struct bpf_insn	insns[MAX_INSNS];
27 	enum bpf_attach_type attach_type;
28 	const char *sysctl;
29 	int open_flags;
30 	const char *newval;
31 	enum {
32 		LOAD_REJECT,
33 		ATTACH_REJECT,
34 		OP_EPERM,
35 		SUCCESS,
36 	} result;
37 };
38 
39 static struct sysctl_test tests[] = {
40 	{
41 		.descr = "sysctl wrong attach_type",
42 		.insns = {
43 			BPF_MOV64_IMM(BPF_REG_0, 1),
44 			BPF_EXIT_INSN(),
45 		},
46 		.attach_type = 0,
47 		.sysctl = "kernel/ostype",
48 		.open_flags = O_RDONLY,
49 		.result = ATTACH_REJECT,
50 	},
51 	{
52 		.descr = "sysctl:read allow all",
53 		.insns = {
54 			BPF_MOV64_IMM(BPF_REG_0, 1),
55 			BPF_EXIT_INSN(),
56 		},
57 		.attach_type = BPF_CGROUP_SYSCTL,
58 		.sysctl = "kernel/ostype",
59 		.open_flags = O_RDONLY,
60 		.result = SUCCESS,
61 	},
62 	{
63 		.descr = "sysctl:read deny all",
64 		.insns = {
65 			BPF_MOV64_IMM(BPF_REG_0, 0),
66 			BPF_EXIT_INSN(),
67 		},
68 		.attach_type = BPF_CGROUP_SYSCTL,
69 		.sysctl = "kernel/ostype",
70 		.open_flags = O_RDONLY,
71 		.result = OP_EPERM,
72 	},
73 	{
74 		.descr = "ctx:write sysctl:read read ok",
75 		.insns = {
76 			/* If (write) */
77 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_1,
78 				    offsetof(struct bpf_sysctl, write)),
79 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 1, 2),
80 
81 			/* return DENY; */
82 			BPF_MOV64_IMM(BPF_REG_0, 0),
83 			BPF_JMP_A(1),
84 
85 			/* else return ALLOW; */
86 			BPF_MOV64_IMM(BPF_REG_0, 1),
87 			BPF_EXIT_INSN(),
88 		},
89 		.attach_type = BPF_CGROUP_SYSCTL,
90 		.sysctl = "kernel/ostype",
91 		.open_flags = O_RDONLY,
92 		.result = SUCCESS,
93 	},
94 	{
95 		.descr = "ctx:write sysctl:write read ok",
96 		.insns = {
97 			/* If (write) */
98 			BPF_LDX_MEM(BPF_B, BPF_REG_7, BPF_REG_1,
99 				    offsetof(struct bpf_sysctl, write)),
100 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 1, 2),
101 
102 			/* return DENY; */
103 			BPF_MOV64_IMM(BPF_REG_0, 0),
104 			BPF_JMP_A(1),
105 
106 			/* else return ALLOW; */
107 			BPF_MOV64_IMM(BPF_REG_0, 1),
108 			BPF_EXIT_INSN(),
109 		},
110 		.attach_type = BPF_CGROUP_SYSCTL,
111 		.sysctl = "kernel/domainname",
112 		.open_flags = O_WRONLY,
113 		.newval = "(none)", /* same as default, should fail anyway */
114 		.result = OP_EPERM,
115 	},
116 	{
117 		.descr = "ctx:write sysctl:read write reject",
118 		.insns = {
119 			/* write = X */
120 			BPF_MOV64_IMM(BPF_REG_0, 0),
121 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
122 				    offsetof(struct bpf_sysctl, write)),
123 			BPF_MOV64_IMM(BPF_REG_0, 1),
124 			BPF_EXIT_INSN(),
125 		},
126 		.attach_type = BPF_CGROUP_SYSCTL,
127 		.sysctl = "kernel/ostype",
128 		.open_flags = O_RDONLY,
129 		.result = LOAD_REJECT,
130 	},
131 };
132 
133 static size_t probe_prog_length(const struct bpf_insn *fp)
134 {
135 	size_t len;
136 
137 	for (len = MAX_INSNS - 1; len > 0; --len)
138 		if (fp[len].code != 0 || fp[len].imm != 0)
139 			break;
140 	return len + 1;
141 }
142 
143 static int load_sysctl_prog(struct sysctl_test *test, const char *sysctl_path)
144 {
145 	struct bpf_insn *prog = test->insns;
146 	struct bpf_load_program_attr attr;
147 	int ret;
148 
149 	memset(&attr, 0, sizeof(struct bpf_load_program_attr));
150 	attr.prog_type = BPF_PROG_TYPE_CGROUP_SYSCTL;
151 	attr.insns = prog;
152 	attr.insns_cnt = probe_prog_length(attr.insns);
153 	attr.license = "GPL";
154 
155 	ret = bpf_load_program_xattr(&attr, bpf_log_buf, BPF_LOG_BUF_SIZE);
156 	if (ret < 0 && test->result != LOAD_REJECT) {
157 		log_err(">>> Loading program error.\n"
158 			">>> Verifier output:\n%s\n-------\n", bpf_log_buf);
159 	}
160 
161 	return ret;
162 }
163 
164 static int access_sysctl(const char *sysctl_path,
165 			 const struct sysctl_test *test)
166 {
167 	int err = 0;
168 	int fd;
169 
170 	fd = open(sysctl_path, test->open_flags | O_CLOEXEC);
171 	if (fd < 0)
172 		return fd;
173 
174 	if (test->open_flags == O_RDONLY) {
175 		char buf[128];
176 
177 		if (read(fd, buf, sizeof(buf)) == -1)
178 			goto err;
179 	} else if (test->open_flags == O_WRONLY) {
180 		if (!test->newval) {
181 			log_err("New value for sysctl is not set");
182 			goto err;
183 		}
184 		if (write(fd, test->newval, strlen(test->newval)) == -1)
185 			goto err;
186 	} else {
187 		log_err("Unexpected sysctl access: neither read nor write");
188 		goto err;
189 	}
190 
191 	goto out;
192 err:
193 	err = -1;
194 out:
195 	close(fd);
196 	return err;
197 }
198 
199 static int run_test_case(int cgfd, struct sysctl_test *test)
200 {
201 	enum bpf_attach_type atype = test->attach_type;
202 	char sysctl_path[128];
203 	int progfd = -1;
204 	int err = 0;
205 
206 	printf("Test case: %s .. ", test->descr);
207 
208 	snprintf(sysctl_path, sizeof(sysctl_path), "/proc/sys/%s",
209 		 test->sysctl);
210 
211 	progfd = load_sysctl_prog(test, sysctl_path);
212 	if (progfd < 0) {
213 		if (test->result == LOAD_REJECT)
214 			goto out;
215 		else
216 			goto err;
217 	}
218 
219 	if (bpf_prog_attach(progfd, cgfd, atype, BPF_F_ALLOW_OVERRIDE) == -1) {
220 		if (test->result == ATTACH_REJECT)
221 			goto out;
222 		else
223 			goto err;
224 	}
225 
226 	if (access_sysctl(sysctl_path, test) == -1) {
227 		if (test->result == OP_EPERM && errno == EPERM)
228 			goto out;
229 		else
230 			goto err;
231 	}
232 
233 	if (test->result != SUCCESS) {
234 		log_err("Unexpected failure");
235 		goto err;
236 	}
237 
238 	goto out;
239 err:
240 	err = -1;
241 out:
242 	/* Detaching w/o checking return code: best effort attempt. */
243 	if (progfd != -1)
244 		bpf_prog_detach(cgfd, atype);
245 	close(progfd);
246 	printf("[%s]\n", err ? "FAIL" : "PASS");
247 	return err;
248 }
249 
250 static int run_tests(int cgfd)
251 {
252 	int passes = 0;
253 	int fails = 0;
254 	int i;
255 
256 	for (i = 0; i < ARRAY_SIZE(tests); ++i) {
257 		if (run_test_case(cgfd, &tests[i]))
258 			++fails;
259 		else
260 			++passes;
261 	}
262 	printf("Summary: %d PASSED, %d FAILED\n", passes, fails);
263 	return fails ? -1 : 0;
264 }
265 
266 int main(int argc, char **argv)
267 {
268 	int cgfd = -1;
269 	int err = 0;
270 
271 	if (setup_cgroup_environment())
272 		goto err;
273 
274 	cgfd = create_and_get_cgroup(CG_PATH);
275 	if (cgfd < 0)
276 		goto err;
277 
278 	if (join_cgroup(CG_PATH))
279 		goto err;
280 
281 	if (run_tests(cgfd))
282 		goto err;
283 
284 	goto out;
285 err:
286 	err = -1;
287 out:
288 	close(cgfd);
289 	cleanup_cgroup_environment();
290 	return err;
291 }
292