Add regression test for nsfs
This commit is contained in:
parent
9c99e782ed
commit
78f098fe88
|
|
@ -0,0 +1,230 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <linux/nsfs.h>
|
||||
#include <poll.h>
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../test.h"
|
||||
|
||||
#define NS_DIR "/proc/self/ns"
|
||||
|
||||
// ns_names contains the namespace names as they appear in readlink output.
|
||||
// For pid_for_children and time_for_children, the readlink output shows "pid" and "time" respectively.
|
||||
// For other namespaces, ns_files and ns_names have identical corresponding strings.
|
||||
char *ns_files[] = { "uts", "mnt", "user" };
|
||||
char *ns_names[] = { "uts", "mnt", "user" };
|
||||
int clone_flags[] = { CLONE_NEWUTS, CLONE_NEWNS, CLONE_NEWUSER };
|
||||
|
||||
FN_TEST(common_fs_operations)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
char buf[1];
|
||||
size_t ns_count = sizeof(ns_files) / sizeof(ns_files[0]);
|
||||
|
||||
for (size_t i = 0; i < ns_count; i++) {
|
||||
memset(path, 0, sizeof(path));
|
||||
snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]);
|
||||
|
||||
TEST_ERRNO(open(path, O_RDWR), EPERM);
|
||||
TEST_ERRNO(open(path, O_WRONLY), EPERM);
|
||||
int nsfd = TEST_SUCC(open(path, O_RDONLY));
|
||||
|
||||
TEST_ERRNO(read(nsfd, buf, 1), EINVAL);
|
||||
TEST_ERRNO(write(nsfd, buf, 1), EBADF);
|
||||
|
||||
struct pollfd ns_pfd = { .fd = nsfd,
|
||||
.events = POLLIN | POLLOUT |
|
||||
POLLRDHUP | POLLPRI |
|
||||
POLLRDNORM };
|
||||
TEST_RES(poll(&ns_pfd, 1, -1),
|
||||
ns_pfd.revents == (POLLIN | POLLOUT | POLLRDNORM));
|
||||
|
||||
struct stat64 stat;
|
||||
TEST_RES(fstat64(nsfd, &stat),
|
||||
stat.st_mode == (0444 | S_IFREG));
|
||||
|
||||
TEST_ERRNO(lseek64(nsfd, 0, SEEK_SET), ESPIPE);
|
||||
}
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(readlink)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
char buf[256];
|
||||
size_t ns_count = sizeof(ns_files) / sizeof(ns_files[0]);
|
||||
|
||||
for (size_t i = 0; i < ns_count; i++) {
|
||||
memset(path, 0, sizeof(path));
|
||||
memset(buf, 0, sizeof(buf));
|
||||
snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]);
|
||||
|
||||
char expected_format[256] = { 0 };
|
||||
int nsfd = TEST_SUCC(open(path, O_RDONLY));
|
||||
struct stat stat;
|
||||
TEST_SUCC(fstat(nsfd, &stat));
|
||||
char *ns_name = ns_names[i];
|
||||
snprintf(expected_format, sizeof(expected_format), "%s:[%u]",
|
||||
ns_name, (unsigned int)stat.st_ino);
|
||||
|
||||
TEST_RES(readlink(path, buf, sizeof(buf) - 1),
|
||||
strcmp(expected_format, buf) == 0);
|
||||
}
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(zombie_process)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
size_t ns_count = sizeof(ns_files) / sizeof(ns_files[0]);
|
||||
|
||||
pid_t pid = fork();
|
||||
TEST_RES(pid >= 0, 1);
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process exits immediately to become zombie
|
||||
exit(0);
|
||||
}
|
||||
|
||||
TEST_SUCC(waitid(P_PID, pid, NULL, WNOWAIT | WEXITED));
|
||||
|
||||
// Try to access zombie process's namespace files
|
||||
for (size_t i = 0; i < ns_count; i++) {
|
||||
char *ns_file = ns_files[i];
|
||||
snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid,
|
||||
ns_files[i]);
|
||||
|
||||
// Should still be able to open zombie's PID/user namespace files
|
||||
if (strcmp(ns_file, "pid") == 0 ||
|
||||
strcmp(ns_file, "user") == 0) {
|
||||
char buf[256] = { 0 };
|
||||
TEST_SUCC(readlink(path, buf, sizeof(buf) - 1));
|
||||
|
||||
int nsfd = TEST_SUCC(open(path, O_RDONLY));
|
||||
TEST_SUCC(close(nsfd));
|
||||
} else {
|
||||
TEST_ERRNO(open(path, O_RDONLY), ENOENT);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUCC(waitpid(pid, NULL, 0));
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(ioctl)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
|
||||
for (size_t i = 0; i < sizeof(ns_files) / sizeof(ns_files[0]); i++) {
|
||||
snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]);
|
||||
|
||||
int nsfd = TEST_SUCC(open(path, O_RDONLY));
|
||||
// NS_GET_USERNS
|
||||
if (strcmp(ns_files[i], "user") != 0) {
|
||||
int userns_fd = TEST_SUCC(ioctl(nsfd, NS_GET_USERNS));
|
||||
TEST_SUCC(close(userns_fd));
|
||||
} else {
|
||||
TEST_ERRNO(ioctl(nsfd, NS_GET_USERNS), EPERM);
|
||||
}
|
||||
|
||||
// NS_GET_PARENT - get parent namespace (should fail for non-hierarchical ns)
|
||||
if (strcmp(ns_files[i], "user") != 0) {
|
||||
TEST_ERRNO(ioctl(nsfd, NS_GET_PARENT), EINVAL);
|
||||
} else {
|
||||
TEST_ERRNO(ioctl(nsfd, NS_GET_PARENT), EPERM);
|
||||
}
|
||||
|
||||
// NS_GET_NSTYPE
|
||||
TEST_RES(ioctl(nsfd, NS_GET_NSTYPE), _ret == clone_flags[i]);
|
||||
|
||||
// NS_GET_OWNER_UID: User namespace only
|
||||
if (strcmp(ns_files[i], "user") != 0) {
|
||||
TEST_ERRNO(ioctl(nsfd, NS_GET_OWNER_UID), EINVAL);
|
||||
} else {
|
||||
TEST_ERRNO(ioctl(nsfd, NS_GET_OWNER_UID, 0), EFAULT);
|
||||
uid_t uid;
|
||||
TEST_SUCC(ioctl(nsfd, NS_GET_OWNER_UID, &uid));
|
||||
}
|
||||
}
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(setns)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
|
||||
// Test setns with current process's namespace (should succeed)
|
||||
for (size_t i = 0; i < sizeof(ns_files) / sizeof(ns_files[0]); i++) {
|
||||
snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]);
|
||||
int nsfd = TEST_SUCC(open(path, O_RDONLY));
|
||||
|
||||
// setns to own namespace should succeed
|
||||
if (strcmp(ns_files[i], "user") != 0) {
|
||||
TEST_SUCC(setns(nsfd, 0));
|
||||
} else {
|
||||
TEST_ERRNO(setns(nsfd, 0), EINVAL);
|
||||
}
|
||||
|
||||
TEST_SUCC(close(nsfd));
|
||||
}
|
||||
|
||||
// Test setns with invalid fd
|
||||
TEST_ERRNO(setns(-1, 0), EBADF);
|
||||
|
||||
// Test setns across fork
|
||||
pid_t pid = fork();
|
||||
TEST_RES(pid >= 0, 1);
|
||||
|
||||
if (pid == 0) {
|
||||
// Child: try to join parent's UTS namespace
|
||||
snprintf(path, sizeof(path), "/proc/%d/ns/uts", getppid());
|
||||
int parent_ns = CHECK(open(path, O_RDONLY));
|
||||
|
||||
CHECK(setns(parent_ns, 0));
|
||||
close(parent_ns);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int status;
|
||||
TEST_RES(waitpid(pid, &status, 0), pid);
|
||||
TEST_RES(WIFEXITED(status), 1);
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(proc_fd_name)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
char buf1[256];
|
||||
char buf2[256];
|
||||
|
||||
for (size_t i = 0; i < sizeof(ns_files) / sizeof(ns_files[0]); i++) {
|
||||
snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]);
|
||||
|
||||
memset(buf1, 0, sizeof(buf1));
|
||||
TEST_SUCC(readlink(path, buf1, sizeof(buf1)));
|
||||
|
||||
int nsfd = TEST_SUCC(open(path, O_RDONLY));
|
||||
memset(path, 0, sizeof(path));
|
||||
sprintf(path, "/proc/self/fd/%d", nsfd);
|
||||
|
||||
memset(buf2, 0, sizeof(buf2));
|
||||
TEST_RES(readlink(path, buf2, sizeof(buf2)),
|
||||
strcmp(buf1, buf2) == 0);
|
||||
}
|
||||
}
|
||||
END_TEST()
|
||||
|
|
@ -45,6 +45,7 @@ mmap/mmap_shared_filebacked
|
|||
mmap/mmap_readahead
|
||||
mmap/mmap_vmrss
|
||||
namespace/mnt_ns
|
||||
namespace/proc_nsfs
|
||||
namespace/setns
|
||||
namespace/unshare
|
||||
process/group_session
|
||||
|
|
|
|||
Loading…
Reference in New Issue