368 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2 3#define _GNU_SOURCE4 5#include <assert.h>6#include <fcntl.h>7#include <limits.h>8#include <sched.h>9#include <stdlib.h>10#include <sys/mount.h>11#include <sys/stat.h>12#include <sys/wait.h>13#include <linux/nsfs.h>14#include <linux/stat.h>15 16#include "statmount.h"17#include "../../kselftest.h"18 19#define NSID_PASS 020#define NSID_FAIL 121#define NSID_SKIP 222#define NSID_ERROR 323 24static void handle_result(int ret, const char *testname)25{26 if (ret == NSID_PASS)27 ksft_test_result_pass("%s\n", testname);28 else if (ret == NSID_FAIL)29 ksft_test_result_fail("%s\n", testname);30 else if (ret == NSID_ERROR)31 ksft_exit_fail_msg("%s\n", testname);32 else33 ksft_test_result_skip("%s\n", testname);34}35 36static inline int wait_for_pid(pid_t pid)37{38 int status, ret;39 40again:41 ret = waitpid(pid, &status, 0);42 if (ret == -1) {43 if (errno == EINTR)44 goto again;45 46 ksft_print_msg("waitpid returned -1, errno=%d\n", errno);47 return -1;48 }49 50 if (!WIFEXITED(status)) {51 ksft_print_msg(52 "waitpid !WIFEXITED, WIFSIGNALED=%d, WTERMSIG=%d\n",53 WIFSIGNALED(status), WTERMSIG(status));54 return -1;55 }56 57 ret = WEXITSTATUS(status);58 return ret;59}60 61static int get_mnt_ns_id(const char *mnt_ns, uint64_t *mnt_ns_id)62{63 int fd = open(mnt_ns, O_RDONLY);64 65 if (fd < 0) {66 ksft_print_msg("failed to open for ns %s: %s\n",67 mnt_ns, strerror(errno));68 sleep(60);69 return NSID_ERROR;70 }71 72 if (ioctl(fd, NS_GET_MNTNS_ID, mnt_ns_id) < 0) {73 ksft_print_msg("failed to get the nsid for ns %s: %s\n",74 mnt_ns, strerror(errno));75 return NSID_ERROR;76 }77 close(fd);78 return NSID_PASS;79}80 81static int get_mnt_id(const char *path, uint64_t *mnt_id)82{83 struct statx sx;84 int ret;85 86 ret = statx(AT_FDCWD, path, 0, STATX_MNT_ID_UNIQUE, &sx);87 if (ret == -1) {88 ksft_print_msg("retrieving unique mount ID for %s: %s\n", path,89 strerror(errno));90 return NSID_ERROR;91 }92 93 if (!(sx.stx_mask & STATX_MNT_ID_UNIQUE)) {94 ksft_print_msg("no unique mount ID available for %s\n", path);95 return NSID_ERROR;96 }97 98 *mnt_id = sx.stx_mnt_id;99 return NSID_PASS;100}101 102static int write_file(const char *path, const char *val)103{104 int fd = open(path, O_WRONLY);105 size_t len = strlen(val);106 int ret;107 108 if (fd == -1) {109 ksft_print_msg("opening %s for write: %s\n", path, strerror(errno));110 return NSID_ERROR;111 }112 113 ret = write(fd, val, len);114 if (ret == -1) {115 ksft_print_msg("writing to %s: %s\n", path, strerror(errno));116 return NSID_ERROR;117 }118 if (ret != len) {119 ksft_print_msg("short write to %s\n", path);120 return NSID_ERROR;121 }122 123 ret = close(fd);124 if (ret == -1) {125 ksft_print_msg("closing %s\n", path);126 return NSID_ERROR;127 }128 129 return NSID_PASS;130}131 132static int setup_namespace(void)133{134 int ret;135 char buf[32];136 uid_t uid = getuid();137 gid_t gid = getgid();138 139 ret = unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWPID);140 if (ret == -1)141 ksft_exit_fail_msg("unsharing mountns and userns: %s\n",142 strerror(errno));143 144 sprintf(buf, "0 %d 1", uid);145 ret = write_file("/proc/self/uid_map", buf);146 if (ret != NSID_PASS)147 return ret;148 ret = write_file("/proc/self/setgroups", "deny");149 if (ret != NSID_PASS)150 return ret;151 sprintf(buf, "0 %d 1", gid);152 ret = write_file("/proc/self/gid_map", buf);153 if (ret != NSID_PASS)154 return ret;155 156 ret = mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);157 if (ret == -1) {158 ksft_print_msg("making mount tree private: %s\n",159 strerror(errno));160 return NSID_ERROR;161 }162 163 return NSID_PASS;164}165 166static int _test_statmount_mnt_ns_id(void)167{168 struct statmount sm;169 uint64_t mnt_ns_id;170 uint64_t root_id;171 int ret;172 173 ret = get_mnt_ns_id("/proc/self/ns/mnt", &mnt_ns_id);174 if (ret != NSID_PASS)175 return ret;176 177 ret = get_mnt_id("/", &root_id);178 if (ret != NSID_PASS)179 return ret;180 181 ret = statmount(root_id, 0, STATMOUNT_MNT_NS_ID, &sm, sizeof(sm), 0);182 if (ret == -1) {183 ksft_print_msg("statmount mnt ns id: %s\n", strerror(errno));184 return NSID_ERROR;185 }186 187 if (sm.size != sizeof(sm)) {188 ksft_print_msg("unexpected size: %u != %u\n", sm.size,189 (uint32_t)sizeof(sm));190 return NSID_FAIL;191 }192 if (sm.mask != STATMOUNT_MNT_NS_ID) {193 ksft_print_msg("statmount mnt ns id unavailable\n");194 return NSID_SKIP;195 }196 197 if (sm.mnt_ns_id != mnt_ns_id) {198 ksft_print_msg("unexpected mnt ns ID: 0x%llx != 0x%llx\n",199 (unsigned long long)sm.mnt_ns_id,200 (unsigned long long)mnt_ns_id);201 return NSID_FAIL;202 }203 204 return NSID_PASS;205}206 207static void test_statmount_mnt_ns_id(void)208{209 pid_t pid;210 int ret;211 212 pid = fork();213 if (pid < 0)214 ksft_exit_fail_msg("failed to fork: %s\n", strerror(errno));215 216 /* We're the original pid, wait for the result. */217 if (pid != 0) {218 ret = wait_for_pid(pid);219 handle_result(ret, "test statmount ns id");220 return;221 }222 223 ret = setup_namespace();224 if (ret != NSID_PASS)225 exit(ret);226 ret = _test_statmount_mnt_ns_id();227 exit(ret);228}229 230static int validate_external_listmount(pid_t pid, uint64_t child_nr_mounts)231{232 uint64_t list[256];233 uint64_t mnt_ns_id;234 uint64_t nr_mounts;235 char buf[256];236 int ret;237 238 /* Get the mount ns id for our child. */239 snprintf(buf, sizeof(buf), "/proc/%lu/ns/mnt", (unsigned long)pid);240 ret = get_mnt_ns_id(buf, &mnt_ns_id);241 242 nr_mounts = listmount(LSMT_ROOT, mnt_ns_id, 0, list, 256, 0);243 if (nr_mounts == (uint64_t)-1) {244 ksft_print_msg("listmount: %s\n", strerror(errno));245 return NSID_ERROR;246 }247 248 if (nr_mounts != child_nr_mounts) {249 ksft_print_msg("listmount results is %zi != %zi\n", nr_mounts,250 child_nr_mounts);251 return NSID_FAIL;252 }253 254 /* Validate that all of our entries match our mnt_ns_id. */255 for (int i = 0; i < nr_mounts; i++) {256 struct statmount sm;257 258 ret = statmount(list[i], mnt_ns_id, STATMOUNT_MNT_NS_ID, &sm,259 sizeof(sm), 0);260 if (ret < 0) {261 ksft_print_msg("statmount mnt ns id: %s\n", strerror(errno));262 return NSID_ERROR;263 }264 265 if (sm.mask != STATMOUNT_MNT_NS_ID) {266 ksft_print_msg("statmount mnt ns id unavailable\n");267 return NSID_SKIP;268 }269 270 if (sm.mnt_ns_id != mnt_ns_id) {271 ksft_print_msg("listmount gave us the wrong ns id: 0x%llx != 0x%llx\n",272 (unsigned long long)sm.mnt_ns_id,273 (unsigned long long)mnt_ns_id);274 return NSID_FAIL;275 }276 }277 278 return NSID_PASS;279}280 281static void test_listmount_ns(void)282{283 uint64_t nr_mounts;284 char pval;285 int child_ready_pipe[2];286 int parent_ready_pipe[2];287 pid_t pid;288 int ret, child_ret;289 290 if (pipe(child_ready_pipe) < 0)291 ksft_exit_fail_msg("failed to create the child pipe: %s\n",292 strerror(errno));293 if (pipe(parent_ready_pipe) < 0)294 ksft_exit_fail_msg("failed to create the parent pipe: %s\n",295 strerror(errno));296 297 pid = fork();298 if (pid < 0)299 ksft_exit_fail_msg("failed to fork: %s\n", strerror(errno));300 301 if (pid == 0) {302 char cval;303 uint64_t list[256];304 305 close(child_ready_pipe[0]);306 close(parent_ready_pipe[1]);307 308 ret = setup_namespace();309 if (ret != NSID_PASS)310 exit(ret);311 312 nr_mounts = listmount(LSMT_ROOT, 0, 0, list, 256, 0);313 if (nr_mounts == (uint64_t)-1) {314 ksft_print_msg("listmount: %s\n", strerror(errno));315 exit(NSID_FAIL);316 }317 318 /*319 * Tell our parent how many mounts we have, and then wait for it320 * to tell us we're done.321 */322 if (write(child_ready_pipe[1], &nr_mounts, sizeof(nr_mounts)) !=323 sizeof(nr_mounts))324 ret = NSID_ERROR;325 if (read(parent_ready_pipe[0], &cval, sizeof(cval)) != sizeof(cval))326 ret = NSID_ERROR;327 exit(NSID_PASS);328 }329 330 close(child_ready_pipe[1]);331 close(parent_ready_pipe[0]);332 333 /* Wait until the child has created everything. */334 if (read(child_ready_pipe[0], &nr_mounts, sizeof(nr_mounts)) !=335 sizeof(nr_mounts))336 ret = NSID_ERROR;337 338 ret = validate_external_listmount(pid, nr_mounts);339 340 if (write(parent_ready_pipe[1], &pval, sizeof(pval)) != sizeof(pval))341 ret = NSID_ERROR;342 343 child_ret = wait_for_pid(pid);344 if (child_ret != NSID_PASS)345 ret = child_ret;346 handle_result(ret, "test listmount ns id");347}348 349int main(void)350{351 int ret;352 353 ksft_print_header();354 ret = statmount(0, 0, 0, NULL, 0, 0);355 assert(ret == -1);356 if (errno == ENOSYS)357 ksft_exit_skip("statmount() syscall not supported\n");358 359 ksft_set_plan(2);360 test_statmount_mnt_ns_id();361 test_listmount_ns();362 363 if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)364 ksft_exit_fail();365 else366 ksft_exit_pass();367}368