1 // SPDX-License-Identifier: GPL-2.0 2 /* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */ 3 #define _GNU_SOURCE 4 #include <test_progs.h> 5 #include <bpf/btf.h> 6 #include <fcntl.h> 7 #include <unistd.h> 8 #include <linux/unistd.h> 9 #include <linux/mount.h> 10 #include <sys/syscall.h> 11 #include "bpf/libbpf_internal.h" 12 13 static inline int sys_fsopen(const char *fsname, unsigned flags) 14 { 15 return syscall(__NR_fsopen, fsname, flags); 16 } 17 18 static inline int sys_fsconfig(int fs_fd, unsigned cmd, const char *key, const void *val, int aux) 19 { 20 return syscall(__NR_fsconfig, fs_fd, cmd, key, val, aux); 21 } 22 23 static inline int sys_fsmount(int fs_fd, unsigned flags, unsigned ms_flags) 24 { 25 return syscall(__NR_fsmount, fs_fd, flags, ms_flags); 26 } 27 28 __attribute__((unused)) 29 static inline int sys_move_mount(int from_dfd, const char *from_path, 30 int to_dfd, const char *to_path, 31 unsigned int ms_flags) 32 { 33 return syscall(__NR_move_mount, from_dfd, from_path, to_dfd, to_path, ms_flags); 34 } 35 36 static void bpf_obj_pinning_detached(void) 37 { 38 LIBBPF_OPTS(bpf_obj_pin_opts, pin_opts); 39 LIBBPF_OPTS(bpf_obj_get_opts, get_opts); 40 int fs_fd = -1, mnt_fd = -1; 41 int map_fd = -1, map_fd2 = -1; 42 int zero = 0, src_value, dst_value, err; 43 const char *map_name = "fsmount_map"; 44 45 /* A bunch of below UAPI calls are constructed based on reading: 46 * https://brauner.io/2023/02/28/mounting-into-mount-namespaces.html 47 */ 48 49 /* create VFS context */ 50 fs_fd = sys_fsopen("bpf", 0); 51 if (!ASSERT_GE(fs_fd, 0, "fs_fd")) 52 goto cleanup; 53 54 /* instantiate FS object */ 55 err = sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0); 56 if (!ASSERT_OK(err, "fs_create")) 57 goto cleanup; 58 59 /* create O_PATH fd for detached mount */ 60 mnt_fd = sys_fsmount(fs_fd, 0, 0); 61 if (!ASSERT_GE(mnt_fd, 0, "mnt_fd")) 62 goto cleanup; 63 64 /* If we wanted to expose detached mount in the file system, we'd do 65 * something like below. But the whole point is that we actually don't 66 * even have to expose BPF FS in the file system to be able to work 67 * (pin/get objects) with it. 68 * 69 * err = sys_move_mount(mnt_fd, "", -EBADF, mnt_path, MOVE_MOUNT_F_EMPTY_PATH); 70 * if (!ASSERT_OK(err, "move_mount")) 71 * goto cleanup; 72 */ 73 74 /* create BPF map to pin */ 75 map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, map_name, 4, 4, 1, NULL); 76 if (!ASSERT_GE(map_fd, 0, "map_fd")) 77 goto cleanup; 78 79 /* pin BPF map into detached BPF FS through mnt_fd */ 80 pin_opts.file_flags = BPF_F_PATH_FD; 81 pin_opts.path_fd = mnt_fd; 82 err = bpf_obj_pin_opts(map_fd, map_name, &pin_opts); 83 if (!ASSERT_OK(err, "map_pin")) 84 goto cleanup; 85 86 /* get BPF map from detached BPF FS through mnt_fd */ 87 get_opts.file_flags = BPF_F_PATH_FD; 88 get_opts.path_fd = mnt_fd; 89 map_fd2 = bpf_obj_get_opts(map_name, &get_opts); 90 if (!ASSERT_GE(map_fd2, 0, "map_get")) 91 goto cleanup; 92 93 /* update map through one FD */ 94 src_value = 0xcafebeef; 95 err = bpf_map_update_elem(map_fd, &zero, &src_value, 0); 96 ASSERT_OK(err, "map_update"); 97 98 /* check values written/read through different FDs do match */ 99 dst_value = 0; 100 err = bpf_map_lookup_elem(map_fd2, &zero, &dst_value); 101 ASSERT_OK(err, "map_lookup"); 102 ASSERT_EQ(dst_value, src_value, "map_value_eq1"); 103 ASSERT_EQ(dst_value, 0xcafebeef, "map_value_eq2"); 104 105 cleanup: 106 if (map_fd >= 0) 107 ASSERT_OK(close(map_fd), "close_map_fd"); 108 if (map_fd2 >= 0) 109 ASSERT_OK(close(map_fd2), "close_map_fd2"); 110 if (fs_fd >= 0) 111 ASSERT_OK(close(fs_fd), "close_fs_fd"); 112 if (mnt_fd >= 0) 113 ASSERT_OK(close(mnt_fd), "close_mnt_fd"); 114 } 115 116 enum path_kind 117 { 118 PATH_STR_ABS, 119 PATH_STR_REL, 120 PATH_FD_REL, 121 }; 122 123 static void validate_pin(int map_fd, const char *map_name, int src_value, 124 enum path_kind path_kind) 125 { 126 LIBBPF_OPTS(bpf_obj_pin_opts, pin_opts); 127 char abs_path[PATH_MAX], old_cwd[PATH_MAX]; 128 const char *pin_path = NULL; 129 int zero = 0, dst_value, map_fd2, err; 130 131 snprintf(abs_path, sizeof(abs_path), "/sys/fs/bpf/%s", map_name); 132 old_cwd[0] = '\0'; 133 134 switch (path_kind) { 135 case PATH_STR_ABS: 136 /* absolute path */ 137 pin_path = abs_path; 138 break; 139 case PATH_STR_REL: 140 /* cwd + relative path */ 141 ASSERT_OK_PTR(getcwd(old_cwd, sizeof(old_cwd)), "getcwd"); 142 ASSERT_OK(chdir("/sys/fs/bpf"), "chdir"); 143 pin_path = map_name; 144 break; 145 case PATH_FD_REL: 146 /* dir fd + relative path */ 147 pin_opts.file_flags = BPF_F_PATH_FD; 148 pin_opts.path_fd = open("/sys/fs/bpf", O_PATH); 149 ASSERT_GE(pin_opts.path_fd, 0, "path_fd"); 150 pin_path = map_name; 151 break; 152 } 153 154 /* pin BPF map using specified path definition */ 155 err = bpf_obj_pin_opts(map_fd, pin_path, &pin_opts); 156 ASSERT_OK(err, "obj_pin"); 157 158 /* cleanup */ 159 if (path_kind == PATH_FD_REL && pin_opts.path_fd >= 0) 160 close(pin_opts.path_fd); 161 if (old_cwd[0]) 162 ASSERT_OK(chdir(old_cwd), "restore_cwd"); 163 164 map_fd2 = bpf_obj_get(abs_path); 165 if (!ASSERT_GE(map_fd2, 0, "map_get")) 166 goto cleanup; 167 168 /* update map through one FD */ 169 err = bpf_map_update_elem(map_fd, &zero, &src_value, 0); 170 ASSERT_OK(err, "map_update"); 171 172 /* check values written/read through different FDs do match */ 173 dst_value = 0; 174 err = bpf_map_lookup_elem(map_fd2, &zero, &dst_value); 175 ASSERT_OK(err, "map_lookup"); 176 ASSERT_EQ(dst_value, src_value, "map_value_eq"); 177 cleanup: 178 if (map_fd2 >= 0) 179 ASSERT_OK(close(map_fd2), "close_map_fd2"); 180 unlink(abs_path); 181 } 182 183 static void validate_get(int map_fd, const char *map_name, int src_value, 184 enum path_kind path_kind) 185 { 186 LIBBPF_OPTS(bpf_obj_get_opts, get_opts); 187 char abs_path[PATH_MAX], old_cwd[PATH_MAX]; 188 const char *pin_path = NULL; 189 int zero = 0, dst_value, map_fd2, err; 190 191 snprintf(abs_path, sizeof(abs_path), "/sys/fs/bpf/%s", map_name); 192 /* pin BPF map using specified path definition */ 193 err = bpf_obj_pin(map_fd, abs_path); 194 if (!ASSERT_OK(err, "pin_map")) 195 return; 196 197 old_cwd[0] = '\0'; 198 199 switch (path_kind) { 200 case PATH_STR_ABS: 201 /* absolute path */ 202 pin_path = abs_path; 203 break; 204 case PATH_STR_REL: 205 /* cwd + relative path */ 206 ASSERT_OK_PTR(getcwd(old_cwd, sizeof(old_cwd)), "getcwd"); 207 ASSERT_OK(chdir("/sys/fs/bpf"), "chdir"); 208 pin_path = map_name; 209 break; 210 case PATH_FD_REL: 211 /* dir fd + relative path */ 212 get_opts.file_flags = BPF_F_PATH_FD; 213 get_opts.path_fd = open("/sys/fs/bpf", O_PATH); 214 ASSERT_GE(get_opts.path_fd, 0, "path_fd"); 215 pin_path = map_name; 216 break; 217 } 218 219 map_fd2 = bpf_obj_get_opts(pin_path, &get_opts); 220 if (!ASSERT_GE(map_fd2, 0, "map_get")) 221 goto cleanup; 222 223 /* cleanup */ 224 if (path_kind == PATH_FD_REL && get_opts.path_fd >= 0) 225 close(get_opts.path_fd); 226 if (old_cwd[0]) 227 ASSERT_OK(chdir(old_cwd), "restore_cwd"); 228 229 /* update map through one FD */ 230 err = bpf_map_update_elem(map_fd, &zero, &src_value, 0); 231 ASSERT_OK(err, "map_update"); 232 233 /* check values written/read through different FDs do match */ 234 dst_value = 0; 235 err = bpf_map_lookup_elem(map_fd2, &zero, &dst_value); 236 ASSERT_OK(err, "map_lookup"); 237 ASSERT_EQ(dst_value, src_value, "map_value_eq"); 238 cleanup: 239 if (map_fd2 >= 0) 240 ASSERT_OK(close(map_fd2), "close_map_fd2"); 241 unlink(abs_path); 242 } 243 244 static void bpf_obj_pinning_mounted(enum path_kind path_kind) 245 { 246 const char *map_name = "mounted_map"; 247 int map_fd; 248 249 /* create BPF map to pin */ 250 map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, map_name, 4, 4, 1, NULL); 251 if (!ASSERT_GE(map_fd, 0, "map_fd")) 252 return; 253 254 validate_pin(map_fd, map_name, 100 + (int)path_kind, path_kind); 255 validate_get(map_fd, map_name, 200 + (int)path_kind, path_kind); 256 ASSERT_OK(close(map_fd), "close_map_fd"); 257 } 258 259 void test_bpf_obj_pinning() 260 { 261 if (test__start_subtest("detached")) 262 bpf_obj_pinning_detached(); 263 if (test__start_subtest("mounted-str-abs")) 264 bpf_obj_pinning_mounted(PATH_STR_ABS); 265 if (test__start_subtest("mounted-str-rel")) 266 bpf_obj_pinning_mounted(PATH_STR_REL); 267 if (test__start_subtest("mounted-fd-rel")) 268 bpf_obj_pinning_mounted(PATH_FD_REL); 269 } 270