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