Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: GPL-2.0-only
/fd-001-lookup
/fd-002-posix-eq
/fd-003-kthread
/proc-2-is-kthread
/proc-fsconfig-hidepid
/proc-loadavg-001
/proc-maps-race
/proc-multiple-procfs
/proc-net-dev-lseek
/proc-empty-vm
/proc-pid-vm
/proc-self-map-files-001
/proc-self-map-files-002
/proc-self-isnt-kthread
/proc-self-syscall
/proc-self-wchan
/proc-subset-pid
/proc-tid0
/proc-uptime-001
/proc-uptime-002
/proc-pidns
/read
/self
/setns-dcache
/setns-sysvipc
/thread-self
@@ -0,0 +1,34 @@
# SPDX-License-Identifier: GPL-2.0-only
CFLAGS += -Wall -O2 -Wno-unused-function
CFLAGS += $(TOOLS_INCLUDES)
LDFLAGS += -pthread
TEST_GEN_PROGS :=
TEST_GEN_PROGS += fd-001-lookup
TEST_GEN_PROGS += fd-002-posix-eq
TEST_GEN_PROGS += fd-003-kthread
TEST_GEN_PROGS += proc-2-is-kthread
TEST_GEN_PROGS += proc-loadavg-001
TEST_GEN_PROGS += proc-maps-race
TEST_GEN_PROGS += proc-net-dev-lseek
TEST_GEN_PROGS += proc-empty-vm
TEST_GEN_PROGS += proc-pid-vm
TEST_GEN_PROGS += proc-self-map-files-001
TEST_GEN_PROGS += proc-self-map-files-002
TEST_GEN_PROGS += proc-self-isnt-kthread
TEST_GEN_PROGS += proc-self-syscall
TEST_GEN_PROGS += proc-self-wchan
TEST_GEN_PROGS += proc-subset-pid
TEST_GEN_PROGS += proc-tid0
TEST_GEN_PROGS += proc-uptime-001
TEST_GEN_PROGS += proc-uptime-002
TEST_GEN_PROGS += read
TEST_GEN_PROGS += self
TEST_GEN_PROGS += setns-dcache
TEST_GEN_PROGS += setns-sysvipc
TEST_GEN_PROGS += thread-self
TEST_GEN_PROGS += proc-multiple-procfs
TEST_GEN_PROGS += proc-fsconfig-hidepid
TEST_GEN_PROGS += proc-pidns
include ../lib.mk
@@ -0,0 +1 @@
CONFIG_PROC_FS=y
@@ -0,0 +1,168 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Test /proc/*/fd lookup.
#undef NDEBUG
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "proc.h"
/* lstat(2) has more "coverage" in case non-symlink pops up somehow. */
static void test_lookup_pass(const char *pathname)
{
struct stat st;
ssize_t rv;
memset(&st, 0, sizeof(struct stat));
rv = lstat(pathname, &st);
assert(rv == 0);
assert(S_ISLNK(st.st_mode));
}
static void test_lookup_fail(const char *pathname)
{
struct stat st;
ssize_t rv;
rv = lstat(pathname, &st);
assert(rv == -1 && errno == ENOENT);
}
static void test_lookup(unsigned int fd)
{
char buf[64];
unsigned int c;
unsigned int u;
int i;
snprintf(buf, sizeof(buf), "/proc/self/fd/%u", fd);
test_lookup_pass(buf);
/* leading junk */
for (c = 1; c <= 255; c++) {
if (c == '/')
continue;
snprintf(buf, sizeof(buf), "/proc/self/fd/%c%u", c, fd);
test_lookup_fail(buf);
}
/* trailing junk */
for (c = 1; c <= 255; c++) {
if (c == '/')
continue;
snprintf(buf, sizeof(buf), "/proc/self/fd/%u%c", fd, c);
test_lookup_fail(buf);
}
for (i = INT_MIN; i < INT_MIN + 1024; i++) {
snprintf(buf, sizeof(buf), "/proc/self/fd/%d", i);
test_lookup_fail(buf);
}
for (i = -1024; i < 0; i++) {
snprintf(buf, sizeof(buf), "/proc/self/fd/%d", i);
test_lookup_fail(buf);
}
for (u = INT_MAX - 1024; u <= (unsigned int)INT_MAX + 1024; u++) {
snprintf(buf, sizeof(buf), "/proc/self/fd/%u", u);
test_lookup_fail(buf);
}
for (u = UINT_MAX - 1024; u != 0; u++) {
snprintf(buf, sizeof(buf), "/proc/self/fd/%u", u);
test_lookup_fail(buf);
}
}
int main(void)
{
struct dirent *de;
unsigned int fd, target_fd;
if (unshare(CLONE_FILES) == -1)
return 1;
/* Wipe fdtable. */
do {
DIR *d;
d = opendir("/proc/self/fd");
if (!d)
return 1;
de = xreaddir(d);
assert(de->d_type == DT_DIR);
assert(streq(de->d_name, "."));
de = xreaddir(d);
assert(de->d_type == DT_DIR);
assert(streq(de->d_name, ".."));
next:
de = xreaddir(d);
if (de) {
unsigned long long fd_ull;
unsigned int fd;
char *end;
assert(de->d_type == DT_LNK);
fd_ull = xstrtoull(de->d_name, &end);
assert(*end == '\0');
assert(fd_ull == (unsigned int)fd_ull);
fd = fd_ull;
if (fd == dirfd(d))
goto next;
close(fd);
}
closedir(d);
} while (de);
/* Now fdtable is clean. */
fd = open("/", O_PATH|O_DIRECTORY);
assert(fd == 0);
test_lookup(fd);
close(fd);
/* Clean again! */
fd = open("/", O_PATH|O_DIRECTORY);
assert(fd == 0);
/* Default RLIMIT_NOFILE-1 */
target_fd = 1023;
while (target_fd > 0) {
if (dup2(fd, target_fd) == target_fd)
break;
target_fd /= 2;
}
assert(target_fd > 0);
close(fd);
test_lookup(target_fd);
close(target_fd);
return 0;
}
@@ -0,0 +1,57 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Test that open(/proc/*/fd/*) opens the same file.
#undef NDEBUG
#include <assert.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd0, fd1, fd2;
struct stat st0, st1, st2;
char buf[64];
int rv;
fd0 = open("/", O_DIRECTORY|O_RDONLY);
assert(fd0 >= 0);
snprintf(buf, sizeof(buf), "/proc/self/fd/%u", fd0);
fd1 = open(buf, O_RDONLY);
assert(fd1 >= 0);
snprintf(buf, sizeof(buf), "/proc/thread-self/fd/%u", fd0);
fd2 = open(buf, O_RDONLY);
assert(fd2 >= 0);
rv = fstat(fd0, &st0);
assert(rv == 0);
rv = fstat(fd1, &st1);
assert(rv == 0);
rv = fstat(fd2, &st2);
assert(rv == 0);
assert(st0.st_dev == st1.st_dev);
assert(st0.st_ino == st1.st_ino);
assert(st0.st_dev == st2.st_dev);
assert(st0.st_ino == st2.st_ino);
return 0;
}
@@ -0,0 +1,178 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Test that /proc/$KERNEL_THREAD/fd/ is empty.
#undef NDEBUG
#include <sys/syscall.h>
#include <assert.h>
#include <dirent.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "proc.h"
#define PF_KHTREAD 0x00200000
/*
* Test for kernel threadness atomically with openat().
*
* Return /proc/$PID/fd descriptor if process is kernel thread.
* Return -1 if a process is userspace process.
*/
static int kernel_thread_fd(unsigned int pid)
{
unsigned int flags = 0;
char buf[4096];
int dir_fd, fd;
ssize_t rv;
snprintf(buf, sizeof(buf), "/proc/%u", pid);
dir_fd = open(buf, O_RDONLY|O_DIRECTORY);
if (dir_fd == -1)
return -1;
/*
* Believe it or not, struct task_struct::flags is directly exposed
* to userspace!
*/
fd = openat(dir_fd, "stat", O_RDONLY);
if (fd == -1) {
close(dir_fd);
return -1;
}
rv = read(fd, buf, sizeof(buf));
close(fd);
if (0 < rv && rv <= sizeof(buf)) {
unsigned long long flags_ull;
char *p, *end;
int i;
assert(buf[rv - 1] == '\n');
buf[rv - 1] = '\0';
/* Search backwards: ->comm can contain whitespace and ')'. */
for (i = 0; i < 43; i++) {
p = strrchr(buf, ' ');
assert(p);
*p = '\0';
}
p = strrchr(buf, ' ');
assert(p);
flags_ull = xstrtoull(p + 1, &end);
assert(*end == '\0');
assert(flags_ull == (unsigned int)flags_ull);
flags = flags_ull;
}
fd = -1;
if (flags & PF_KHTREAD) {
fd = openat(dir_fd, "fd", O_RDONLY|O_DIRECTORY);
}
close(dir_fd);
return fd;
}
static void test_readdir(int fd)
{
DIR *d;
struct dirent *de;
d = fdopendir(fd);
assert(d);
de = xreaddir(d);
assert(streq(de->d_name, "."));
assert(de->d_type == DT_DIR);
de = xreaddir(d);
assert(streq(de->d_name, ".."));
assert(de->d_type == DT_DIR);
de = xreaddir(d);
assert(!de);
}
static inline int sys_statx(int dirfd, const char *pathname, int flags,
unsigned int mask, void *stx)
{
return syscall(SYS_statx, dirfd, pathname, flags, mask, stx);
}
static void test_lookup_fail(int fd, const char *pathname)
{
char stx[256] __attribute__((aligned(8)));
int rv;
rv = sys_statx(fd, pathname, AT_SYMLINK_NOFOLLOW, 0, (void *)stx);
assert(rv == -1 && errno == ENOENT);
}
static void test_lookup(int fd)
{
char buf[64];
unsigned int u;
int i;
for (i = INT_MIN; i < INT_MIN + 1024; i++) {
snprintf(buf, sizeof(buf), "%d", i);
test_lookup_fail(fd, buf);
}
for (i = -1024; i < 1024; i++) {
snprintf(buf, sizeof(buf), "%d", i);
test_lookup_fail(fd, buf);
}
for (u = INT_MAX - 1024; u < (unsigned int)INT_MAX + 1024; u++) {
snprintf(buf, sizeof(buf), "%u", u);
test_lookup_fail(fd, buf);
}
for (u = UINT_MAX - 1024; u != 0; u++) {
snprintf(buf, sizeof(buf), "%u", u);
test_lookup_fail(fd, buf);
}
}
int main(void)
{
unsigned int pid;
int fd;
/*
* In theory this will loop indefinitely if kernel threads are exiled
* from /proc.
*
* Start with kthreadd.
*/
pid = 2;
while ((fd = kernel_thread_fd(pid)) == -1 && pid < 1024) {
pid++;
}
/* EACCES if run as non-root. */
if (pid >= 1024)
return 1;
test_readdir(fd);
test_lookup(fd);
return 0;
}
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2024 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* Test that kernel thread is reported as such. */
#undef NDEBUG
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
/*
* The following solutions don't really work:
*
* 1) jit kernel module which creates kernel thread:
* test becomes arch-specific,
* problems with mandatory module signing,
* problems with lockdown mode,
* doesn't work with CONFIG_MODULES=n at all,
* kthread creation API is formally unstable internal kernel API,
* need a mechanism to report test kernel thread's PID back,
*
* 2) ksoftirqd/0 and kswapd0 look like stable enough kernel threads,
* but their PIDs are unstable.
*
* Check against kthreadd which always seem to exist under pid 2.
*/
int fd = open("/proc/2/status", O_RDONLY);
assert(fd >= 0);
char buf[4096];
ssize_t rv = read(fd, buf, sizeof(buf));
assert(0 <= rv && rv < sizeof(buf));
buf[rv] = '\0';
assert(strstr(buf, "Kthread:\t1\n"));
return 0;
}
@@ -0,0 +1,541 @@
#if defined __amd64__ || defined __i386__
/*
* Copyright (c) 2022 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Create a process without mappings by unmapping everything at once and
* holding it with ptrace(2). See what happens to
*
* /proc/${pid}/maps
* /proc/${pid}/numa_maps
* /proc/${pid}/smaps
* /proc/${pid}/smaps_rollup
*/
#undef _GNU_SOURCE
#define _GNU_SOURCE
#undef NDEBUG
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ptrace.h>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#ifdef __amd64__
#define TEST_VSYSCALL
#endif
#if defined __amd64__
#ifndef SYS_pkey_alloc
#define SYS_pkey_alloc 330
#endif
#ifndef SYS_pkey_free
#define SYS_pkey_free 331
#endif
#elif defined __i386__
#ifndef SYS_pkey_alloc
#define SYS_pkey_alloc 381
#endif
#ifndef SYS_pkey_free
#define SYS_pkey_free 382
#endif
#else
#error "SYS_pkey_alloc"
#endif
static int g_protection_key_support;
static int protection_key_support(void)
{
long rv = syscall(SYS_pkey_alloc, 0, 0);
if (rv > 0) {
syscall(SYS_pkey_free, (int)rv);
return 1;
} else if (rv == -1 && errno == ENOSYS) {
return 0;
} else if (rv == -1 && errno == EINVAL) {
// ospke=n
return 0;
} else {
fprintf(stderr, "%s: error: rv %ld, errno %d\n", __func__, rv, errno);
exit(EXIT_FAILURE);
}
}
/*
* 0: vsyscall VMA doesn't exist vsyscall=none
* 1: vsyscall VMA is --xp vsyscall=xonly
* 2: vsyscall VMA is r-xp vsyscall=emulate
*/
static volatile int g_vsyscall;
static const char *g_proc_pid_maps_vsyscall;
static const char *g_proc_pid_smaps_vsyscall;
static const char proc_pid_maps_vsyscall_0[] = "";
static const char proc_pid_maps_vsyscall_1[] =
"ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n";
static const char proc_pid_maps_vsyscall_2[] =
"ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]\n";
static const char proc_pid_smaps_vsyscall_0[] = "";
static const char proc_pid_smaps_vsyscall_1[] =
"ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n"
"Size: 4 kB\n"
"KernelPageSize: 4 kB\n"
"MMUPageSize: 4 kB\n"
"Rss: 0 kB\n"
"Pss: 0 kB\n"
"Pss_Dirty: 0 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
"Private_Dirty: 0 kB\n"
"Referenced: 0 kB\n"
"Anonymous: 0 kB\n"
"KSM: 0 kB\n"
"LazyFree: 0 kB\n"
"AnonHugePages: 0 kB\n"
"ShmemPmdMapped: 0 kB\n"
"FilePmdMapped: 0 kB\n"
"Shared_Hugetlb: 0 kB\n"
"Private_Hugetlb: 0 kB\n"
"Swap: 0 kB\n"
"SwapPss: 0 kB\n"
"Locked: 0 kB\n"
"THPeligible: 0\n"
;
static const char proc_pid_smaps_vsyscall_2[] =
"ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]\n"
"Size: 4 kB\n"
"KernelPageSize: 4 kB\n"
"MMUPageSize: 4 kB\n"
"Rss: 0 kB\n"
"Pss: 0 kB\n"
"Pss_Dirty: 0 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
"Private_Dirty: 0 kB\n"
"Referenced: 0 kB\n"
"Anonymous: 0 kB\n"
"KSM: 0 kB\n"
"LazyFree: 0 kB\n"
"AnonHugePages: 0 kB\n"
"ShmemPmdMapped: 0 kB\n"
"FilePmdMapped: 0 kB\n"
"Shared_Hugetlb: 0 kB\n"
"Private_Hugetlb: 0 kB\n"
"Swap: 0 kB\n"
"SwapPss: 0 kB\n"
"Locked: 0 kB\n"
"THPeligible: 0\n"
;
static void sigaction_SIGSEGV(int _, siginfo_t *__, void *___)
{
_exit(EXIT_FAILURE);
}
#ifdef TEST_VSYSCALL
static void sigaction_SIGSEGV_vsyscall(int _, siginfo_t *__, void *___)
{
_exit(g_vsyscall);
}
/*
* vsyscall page can't be unmapped, probe it directly.
*/
static void vsyscall(void)
{
pid_t pid;
int wstatus;
pid = fork();
if (pid < 0) {
fprintf(stderr, "fork, errno %d\n", errno);
exit(1);
}
if (pid == 0) {
setrlimit(RLIMIT_CORE, &(struct rlimit){});
/* Hide "segfault at ffffffffff600000" messages. */
struct sigaction act = {};
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = sigaction_SIGSEGV_vsyscall;
sigaction(SIGSEGV, &act, NULL);
g_vsyscall = 0;
/* gettimeofday(NULL, NULL); */
uint64_t rax = 0xffffffffff600000;
asm volatile (
"call *%[rax]"
: [rax] "+a" (rax)
: "D" (NULL), "S" (NULL)
: "rcx", "r11"
);
g_vsyscall = 1;
*(volatile int *)0xffffffffff600000UL;
g_vsyscall = 2;
exit(g_vsyscall);
}
waitpid(pid, &wstatus, 0);
if (WIFEXITED(wstatus)) {
g_vsyscall = WEXITSTATUS(wstatus);
} else {
fprintf(stderr, "error: vsyscall wstatus %08x\n", wstatus);
exit(1);
}
}
#endif
static int test_proc_pid_maps(pid_t pid)
{
char buf[4096];
snprintf(buf, sizeof(buf), "/proc/%u/maps", pid);
int fd = open(buf, O_RDONLY);
if (fd == -1) {
perror("open /proc/${pid}/maps");
return EXIT_FAILURE;
} else {
ssize_t rv = read(fd, buf, sizeof(buf));
close(fd);
if (g_vsyscall == 0) {
assert(rv == 0);
} else {
size_t len = strlen(g_proc_pid_maps_vsyscall);
assert(rv == len);
assert(memcmp(buf, g_proc_pid_maps_vsyscall, len) == 0);
}
return EXIT_SUCCESS;
}
}
static int test_proc_pid_numa_maps(pid_t pid)
{
char buf[4096];
snprintf(buf, sizeof(buf), "/proc/%u/numa_maps", pid);
int fd = open(buf, O_RDONLY);
if (fd == -1) {
if (errno == ENOENT) {
/*
* /proc/${pid}/numa_maps is under CONFIG_NUMA,
* it doesn't necessarily exist.
*/
return EXIT_SUCCESS;
}
perror("open /proc/${pid}/numa_maps");
return EXIT_FAILURE;
} else {
ssize_t rv = read(fd, buf, sizeof(buf));
close(fd);
assert(rv == 0);
return EXIT_SUCCESS;
}
}
static int test_proc_pid_smaps(pid_t pid)
{
char buf[4096];
snprintf(buf, sizeof(buf), "/proc/%u/smaps", pid);
int fd = open(buf, O_RDONLY);
if (fd == -1) {
if (errno == ENOENT) {
/*
* /proc/${pid}/smaps is under CONFIG_PROC_PAGE_MONITOR,
* it doesn't necessarily exist.
*/
return EXIT_SUCCESS;
}
perror("open /proc/${pid}/smaps");
return EXIT_FAILURE;
}
ssize_t rv = read(fd, buf, sizeof(buf));
close(fd);
assert(0 <= rv);
assert(rv <= sizeof(buf));
if (g_vsyscall == 0) {
assert(rv == 0);
} else {
size_t len = strlen(g_proc_pid_smaps_vsyscall);
assert(rv > len);
assert(memcmp(buf, g_proc_pid_smaps_vsyscall, len) == 0);
if (g_protection_key_support) {
#define PROTECTION_KEY "ProtectionKey: 0\n"
assert(memmem(buf, rv, PROTECTION_KEY, strlen(PROTECTION_KEY)));
}
}
return EXIT_SUCCESS;
}
static const char g_smaps_rollup[] =
"00000000-00000000 ---p 00000000 00:00 0 [rollup]\n"
"Rss: 0 kB\n"
"Pss: 0 kB\n"
"Pss_Dirty: 0 kB\n"
"Pss_Anon: 0 kB\n"
"Pss_File: 0 kB\n"
"Pss_Shmem: 0 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
"Private_Dirty: 0 kB\n"
"Referenced: 0 kB\n"
"Anonymous: 0 kB\n"
"KSM: 0 kB\n"
"LazyFree: 0 kB\n"
"AnonHugePages: 0 kB\n"
"ShmemPmdMapped: 0 kB\n"
"FilePmdMapped: 0 kB\n"
"Shared_Hugetlb: 0 kB\n"
"Private_Hugetlb: 0 kB\n"
"Swap: 0 kB\n"
"SwapPss: 0 kB\n"
"Locked: 0 kB\n"
;
static int test_proc_pid_smaps_rollup(pid_t pid)
{
char buf[4096];
snprintf(buf, sizeof(buf), "/proc/%u/smaps_rollup", pid);
int fd = open(buf, O_RDONLY);
if (fd == -1) {
if (errno == ENOENT) {
/*
* /proc/${pid}/smaps_rollup is under CONFIG_PROC_PAGE_MONITOR,
* it doesn't necessarily exist.
*/
return EXIT_SUCCESS;
}
perror("open /proc/${pid}/smaps_rollup");
return EXIT_FAILURE;
} else {
ssize_t rv = read(fd, buf, sizeof(buf));
close(fd);
assert(rv == sizeof(g_smaps_rollup) - 1);
assert(memcmp(buf, g_smaps_rollup, sizeof(g_smaps_rollup) - 1) == 0);
return EXIT_SUCCESS;
}
}
static const char *parse_u64(const char *p, const char *const end, uint64_t *rv)
{
*rv = 0;
for (; p != end; p += 1) {
if ('0' <= *p && *p <= '9') {
assert(!__builtin_mul_overflow(*rv, 10, rv));
assert(!__builtin_add_overflow(*rv, *p - '0', rv));
} else {
break;
}
}
assert(p != end);
return p;
}
/*
* There seems to be 2 types of valid output:
* "0 A A B 0 0 0\n" for dynamic exeuctables,
* "0 0 0 B 0 0 0\n" for static executables.
*/
static int test_proc_pid_statm(pid_t pid)
{
char buf[4096];
snprintf(buf, sizeof(buf), "/proc/%u/statm", pid);
int fd = open(buf, O_RDONLY);
if (fd == -1) {
perror("open /proc/${pid}/statm");
return EXIT_FAILURE;
}
ssize_t rv = read(fd, buf, sizeof(buf));
close(fd);
assert(rv >= 0);
assert(rv <= sizeof(buf));
const char *p = buf;
const char *const end = p + rv;
/* size */
assert(p != end && *p++ == '0');
assert(p != end && *p++ == ' ');
uint64_t resident;
p = parse_u64(p, end, &resident);
assert(p != end && *p++ == ' ');
uint64_t shared;
p = parse_u64(p, end, &shared);
assert(p != end && *p++ == ' ');
uint64_t text;
p = parse_u64(p, end, &text);
assert(p != end && *p++ == ' ');
assert(p != end && *p++ == '0');
assert(p != end && *p++ == ' ');
/* data */
assert(p != end && *p++ == '0');
assert(p != end && *p++ == ' ');
assert(p != end && *p++ == '0');
assert(p != end && *p++ == '\n');
assert(p == end);
/*
* "text" is "mm->end_code - mm->start_code" at execve(2) time.
* munmap() doesn't change it. It can be anything (just link
* statically). It can't be 0 because executing to this point
* implies at least 1 page of code.
*/
assert(text > 0);
/*
* These two are always equal. Always 0 for statically linked
* executables and sometimes 0 for dynamically linked executables.
* There is no way to tell one from another without parsing ELF
* which is too much for this test.
*/
assert(resident == shared);
return EXIT_SUCCESS;
}
int main(void)
{
int rv = EXIT_SUCCESS;
#ifdef TEST_VSYSCALL
vsyscall();
#endif
switch (g_vsyscall) {
case 0:
g_proc_pid_maps_vsyscall = proc_pid_maps_vsyscall_0;
g_proc_pid_smaps_vsyscall = proc_pid_smaps_vsyscall_0;
break;
case 1:
g_proc_pid_maps_vsyscall = proc_pid_maps_vsyscall_1;
g_proc_pid_smaps_vsyscall = proc_pid_smaps_vsyscall_1;
break;
case 2:
g_proc_pid_maps_vsyscall = proc_pid_maps_vsyscall_2;
g_proc_pid_smaps_vsyscall = proc_pid_smaps_vsyscall_2;
break;
default:
abort();
}
g_protection_key_support = protection_key_support();
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return EXIT_FAILURE;
} else if (pid == 0) {
rv = ptrace(PTRACE_TRACEME, 0, NULL, NULL);
if (rv != 0) {
if (errno == EPERM) {
fprintf(stderr,
"Did you know? ptrace(PTRACE_TRACEME) doesn't work under strace.\n"
);
kill(getppid(), SIGTERM);
return EXIT_FAILURE;
}
perror("ptrace PTRACE_TRACEME");
return EXIT_FAILURE;
}
/*
* Hide "segfault at ..." messages. Signal handler won't run.
*/
struct sigaction act = {};
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = sigaction_SIGSEGV;
sigaction(SIGSEGV, &act, NULL);
#ifdef __amd64__
munmap(NULL, ((size_t)1 << 47) - 4096);
#elif defined __i386__
{
size_t len;
for (len = -4096;; len -= 4096) {
munmap(NULL, len);
}
}
#else
#error "implement 'unmap everything'"
#endif
return EXIT_FAILURE;
} else {
/*
* TODO find reliable way to signal parent that munmap(2) completed.
* Child can't do it directly because it effectively doesn't exist
* anymore. Looking at child's VM files isn't 100% reliable either:
* due to a bug they may not become empty or empty-like.
*/
sleep(1);
if (rv == EXIT_SUCCESS) {
rv = test_proc_pid_maps(pid);
}
if (rv == EXIT_SUCCESS) {
rv = test_proc_pid_numa_maps(pid);
}
if (rv == EXIT_SUCCESS) {
rv = test_proc_pid_smaps(pid);
}
if (rv == EXIT_SUCCESS) {
rv = test_proc_pid_smaps_rollup(pid);
}
if (rv == EXIT_SUCCESS) {
rv = test_proc_pid_statm(pid);
}
/* Cut the rope. */
int wstatus;
waitpid(pid, &wstatus, 0);
assert(WIFSTOPPED(wstatus));
assert(WSTOPSIG(wstatus) == SIGSEGV);
}
return rv;
}
#else
int main(void)
{
return 4;
}
#endif
@@ -0,0 +1,50 @@
/*
* Copyright © 2020 Alexey Gladkov <gladkov.alexey@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <linux/mount.h>
#include <linux/unistd.h>
static inline int fsopen(const char *fsname, unsigned int flags)
{
return syscall(__NR_fsopen, fsname, flags);
}
static inline int fsconfig(int fd, unsigned int cmd, const char *key, const void *val, int aux)
{
return syscall(__NR_fsconfig, fd, cmd, key, val, aux);
}
int main(void)
{
int fsfd, ret;
int hidepid = 2;
assert((fsfd = fsopen("proc", 0)) != -1);
ret = fsconfig(fsfd, FSCONFIG_SET_BINARY, "hidepid", &hidepid, 0);
assert(ret == -1);
assert(errno == EINVAL);
assert(!fsconfig(fsfd, FSCONFIG_SET_STRING, "hidepid", "2", 0));
assert(!fsconfig(fsfd, FSCONFIG_SET_STRING, "hidepid", "invisible", 0));
assert(!close(fsfd));
return 0;
}
@@ -0,0 +1,82 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* Test that /proc/loadavg correctly reports last pid in pid namespace. */
#include <errno.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
int wstatus;
if (unshare(CLONE_NEWPID) == -1) {
if (errno == ENOSYS || errno == EPERM)
return 4;
return 1;
}
pid = fork();
if (pid == -1)
return 1;
if (pid == 0) {
char buf[128], *p;
int fd;
ssize_t rv;
fd = open("/proc/loadavg" , O_RDONLY);
if (fd == -1)
return 1;
rv = read(fd, buf, sizeof(buf));
if (rv < 3)
return 1;
p = buf + rv;
/* pid 1 */
if (!(p[-3] == ' ' && p[-2] == '1' && p[-1] == '\n'))
return 1;
pid = fork();
if (pid == -1)
return 1;
if (pid == 0)
return 0;
if (waitpid(pid, NULL, 0) == -1)
return 1;
lseek(fd, 0, SEEK_SET);
rv = read(fd, buf, sizeof(buf));
if (rv < 3)
return 1;
p = buf + rv;
/* pid 2 */
if (!(p[-3] == ' ' && p[-2] == '2' && p[-1] == '\n'))
return 1;
return 0;
}
if (waitpid(pid, &wstatus, 0) == -1)
return 1;
if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0)
return 0;
return 1;
}
@@ -0,0 +1,975 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2022 Google LLC.
* Author: Suren Baghdasaryan <surenb@google.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Fork a child that concurrently modifies address space while the main
* process is reading /proc/$PID/maps and /proc/$PID/smaps, verifying the
* results. Address space modifications include:
* VMA splitting and merging
*
*/
#define _GNU_SOURCE
#include "kselftest_harness.h"
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#define min(a, b) \
({ \
typeof(a) _a = (a); \
typeof(b) _b = (b); \
_a < _b ? _a : _b; \
})
/* /proc/pid/maps parsing routines */
struct page_content {
char *data;
ssize_t size;
};
#define LINE_MAX_SIZE 256
struct line_content {
char text[LINE_MAX_SIZE];
unsigned long start_addr;
unsigned long end_addr;
};
enum test_state {
INIT,
CHILD_READY,
PARENT_READY,
SETUP_READY,
SETUP_MODIFY_MAPS,
SETUP_MAPS_MODIFIED,
SETUP_RESTORE_MAPS,
SETUP_MAPS_RESTORED,
TEST_READY,
TEST_DONE,
};
enum maps_file {
MAPS,
SMAPS,
};
struct vma_modifier_info;
FIXTURE(proc_maps_race)
{
struct vma_modifier_info *mod_info;
struct page_content page1;
struct page_content page2;
struct line_content last_line;
struct line_content first_line;
unsigned long duration_sec;
enum maps_file maps_file;
int shared_mem_size;
int skip_pages;
int page_size;
int vma_count;
bool verbose;
int maps_fd;
pid_t pid;
};
FIXTURE_VARIANT(proc_maps_race)
{
const enum maps_file maps_file;
};
FIXTURE_VARIANT_ADD(proc_maps_race, maps) {
.maps_file = MAPS,
};
FIXTURE_VARIANT_ADD(proc_maps_race, smaps) {
.maps_file = SMAPS,
};
typedef bool (*vma_modifier_op)(FIXTURE_DATA(proc_maps_race) *self);
typedef bool (*vma_mod_result_check_op)(struct line_content *mod_last_line,
struct line_content *mod_first_line,
struct line_content *restored_last_line,
struct line_content *restored_first_line);
struct vma_modifier_info {
int vma_count;
void *addr;
int prot;
void *next_addr;
vma_modifier_op vma_modify;
vma_modifier_op vma_restore;
vma_mod_result_check_op vma_mod_check;
pthread_mutex_t sync_lock;
pthread_cond_t sync_cond;
enum test_state curr_state;
bool exit;
void *child_mapped_addr[];
};
static bool read_page(FIXTURE_DATA(proc_maps_race) *self,
struct page_content *page)
{
ssize_t bytes_read;
bytes_read = read(self->maps_fd, page->data, self->page_size);
if (bytes_read <= 0)
return false;
/* Make sure data always ends with a newline character. */
if (page->data[bytes_read - 1] != '\n')
return false;
page->size = bytes_read;
return true;
}
static bool parse_vma_line(char *line_start, char *line_end,
unsigned long *start, unsigned long *end)
{
bool found;
*line_end = '\0'; /* stop sscanf at the EOL */
found = (sscanf(line_start, "%lx-%lx", start, end) == 2);
*line_end = '\n';
return found;
}
static int locate_containing_page(FIXTURE_DATA(proc_maps_race) *self,
unsigned long addr, unsigned long size)
{
unsigned long start, end;
int page = 0;
if (lseek(self->maps_fd, 0, SEEK_SET) < 0)
return -1;
while (true) {
char *curr_pos;
char *end_pos;
if (!read_page(self, &self->page1))
return -1;
curr_pos = self->page1.data;
end_pos = self->page1.data + self->page1.size;
while (curr_pos < end_pos) {
char *line_end;
line_end = strchr(curr_pos, '\n');
if (!line_end)
break;
if (parse_vma_line(curr_pos, line_end, &start, &end) &&
start == addr && end == addr + size)
return page;
curr_pos = line_end + 1;
}
page++;
}
return 0;
}
static bool read_two_pages(FIXTURE_DATA(proc_maps_race) *self)
{
if (lseek(self->maps_fd, 0, SEEK_SET) < 0)
return false;
for (int i = 0; i < self->skip_pages; i++)
if (!read_page(self, &self->page1))
return false;
return read_page(self, &self->page1) && read_page(self, &self->page2);
}
static void copy_line(const char *line_start, const char *line_end,
char *buf, size_t buf_size)
{
size_t len = min(line_end - line_start, buf_size - 1);
strncpy(buf, line_start, len);
buf[len] = '\0';
}
static void copy_first_line(struct page_content *page, char *first_line,
size_t line_size)
{
copy_line(page->data, strchr(page->data, '\n'), first_line, line_size);
}
static void copy_last_line(struct page_content *page, char *last_line,
size_t line_size)
{
/* Get the last line in the first page */
const char *end = page->data + page->size - 1;
/* skip last newline */
const char *pos = end - 1;
/* search previous newline */
while (pos[-1] != '\n')
pos--;
copy_line(pos, end, last_line, line_size);
}
static bool copy_first_entry(struct page_content *page, char *first_line,
size_t line_size)
{
char *start_pos = page->data;
while (start_pos < page->data + page->size) {
unsigned long start_addr;
unsigned long end_addr;
char *end_pos;
end_pos = strchr(start_pos, '\n');
if (!end_pos)
break;
if (parse_vma_line(start_pos, end_pos, &start_addr, &end_addr)) {
copy_line(start_pos, end_pos, first_line, line_size);
return true;
}
start_pos = end_pos + 1;
}
return false;
}
static bool copy_last_entry(struct page_content *page, char *last_line,
size_t line_size)
{
char *end_pos = page->data + page->size - 1;
char *start_pos;
while (end_pos > page->data) {
unsigned long start_addr;
unsigned long end_addr;
/* skip last newline */
start_pos = end_pos - 1;
/* search previous newline */
while (start_pos > page->data && start_pos[-1] != '\n')
start_pos--;
if (parse_vma_line(start_pos, end_pos, &start_addr, &end_addr)) {
copy_line(start_pos, end_pos, last_line, line_size);
return true;
}
end_pos = start_pos - 1;
}
return false;
}
/* Read the last line of the first page and the first line of the second page */
static bool read_boundary_lines(FIXTURE_DATA(proc_maps_race) *self,
struct line_content *last_line,
struct line_content *first_line)
{
if (!read_two_pages(self))
return false;
if (self->maps_file == MAPS) {
copy_last_line(&self->page1, last_line->text, LINE_MAX_SIZE);
copy_first_line(&self->page2, first_line->text, LINE_MAX_SIZE);
} else if (self->maps_file == SMAPS) {
if (!copy_last_entry(&self->page1, last_line->text, LINE_MAX_SIZE) ||
!copy_first_entry(&self->page2, first_line->text, LINE_MAX_SIZE))
return false;
} else {
return false;
}
return sscanf(last_line->text, "%lx-%lx", &last_line->start_addr,
&last_line->end_addr) == 2 &&
sscanf(first_line->text, "%lx-%lx", &first_line->start_addr,
&first_line->end_addr) == 2;
}
/* Thread synchronization routines */
static void wait_for_state(struct vma_modifier_info *mod_info, enum test_state state)
{
pthread_mutex_lock(&mod_info->sync_lock);
while (mod_info->curr_state != state)
pthread_cond_wait(&mod_info->sync_cond, &mod_info->sync_lock);
pthread_mutex_unlock(&mod_info->sync_lock);
}
static void signal_state(struct vma_modifier_info *mod_info, enum test_state state)
{
pthread_mutex_lock(&mod_info->sync_lock);
mod_info->curr_state = state;
pthread_cond_signal(&mod_info->sync_cond);
pthread_mutex_unlock(&mod_info->sync_lock);
}
static void stop_vma_modifier(struct vma_modifier_info *mod_info)
{
wait_for_state(mod_info, SETUP_READY);
mod_info->exit = true;
signal_state(mod_info, SETUP_MODIFY_MAPS);
}
static void print_first_lines(char *text, int nr)
{
const char *end = text;
while (nr && (end = strchr(end, '\n')) != NULL) {
nr--;
end++;
}
if (end) {
int offs = end - text;
text[offs] = '\0';
printf("%s", text);
text[offs] = '\n';
printf("\n");
} else {
printf("%s", text);
}
}
static void print_last_lines(char *text, int nr)
{
const char *start = text + strlen(text);
nr++; /* to ignore the last newline */
while (nr) {
while (start > text && *start != '\n')
start--;
nr--;
start--;
}
printf("%s", start);
}
static void print_boundaries(const char *title, FIXTURE_DATA(proc_maps_race) *self)
{
if (!self->verbose)
return;
printf("%s", title);
/* Print 3 boundary lines from each page */
print_last_lines(self->page1.data, 3);
printf("-----------------page boundary-----------------\n");
print_first_lines(self->page2.data, 3);
}
static bool print_boundaries_on(bool condition, const char *title,
FIXTURE_DATA(proc_maps_race) *self)
{
if (self->verbose && condition)
print_boundaries(title, self);
return condition;
}
static void report_test_start(const char *name, bool verbose)
{
if (verbose)
printf("==== %s ====\n", name);
}
static struct timespec print_ts;
static void start_test_loop(struct timespec *ts, bool verbose)
{
if (verbose)
print_ts.tv_sec = ts->tv_sec;
}
static void end_test_iteration(struct timespec *ts, bool verbose)
{
if (!verbose)
return;
/* Update every second */
if (print_ts.tv_sec == ts->tv_sec)
return;
printf(".");
fflush(stdout);
print_ts.tv_sec = ts->tv_sec;
}
static void end_test_loop(bool verbose)
{
if (verbose)
printf("\n");
}
static bool capture_mod_pattern(FIXTURE_DATA(proc_maps_race) *self,
struct line_content *mod_last_line,
struct line_content *mod_first_line,
struct line_content *restored_last_line,
struct line_content *restored_first_line)
{
print_boundaries("Before modification", self);
signal_state(self->mod_info, SETUP_MODIFY_MAPS);
wait_for_state(self->mod_info, SETUP_MAPS_MODIFIED);
/* Copy last line of the first page and first line of the last page */
if (!read_boundary_lines(self, mod_last_line, mod_first_line))
return false;
print_boundaries("After modification", self);
signal_state(self->mod_info, SETUP_RESTORE_MAPS);
wait_for_state(self->mod_info, SETUP_MAPS_RESTORED);
/* Copy last line of the first page and first line of the last page */
if (!read_boundary_lines(self, restored_last_line, restored_first_line))
return false;
print_boundaries("After restore", self);
if (!self->mod_info->vma_mod_check(mod_last_line, mod_first_line,
restored_last_line, restored_first_line))
return false;
/*
* The content of these lines after modify+resore should be the same
* as the original.
*/
return strcmp(restored_last_line->text, self->last_line.text) == 0 &&
strcmp(restored_first_line->text, self->first_line.text) == 0;
}
static bool query_addr_at(int maps_fd, void *addr,
unsigned long *vma_start, unsigned long *vma_end)
{
struct procmap_query q;
memset(&q, 0, sizeof(q));
q.size = sizeof(q);
/* Find the VMA at the split address */
q.query_addr = (unsigned long long)addr;
q.query_flags = 0;
if (ioctl(maps_fd, PROCMAP_QUERY, &q))
return false;
*vma_start = q.vma_start;
*vma_end = q.vma_end;
return true;
}
static inline bool split_vma(FIXTURE_DATA(proc_maps_race) *self)
{
return mmap(self->mod_info->addr, self->page_size, self->mod_info->prot | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED;
}
static inline bool merge_vma(FIXTURE_DATA(proc_maps_race) *self)
{
return mmap(self->mod_info->addr, self->page_size, self->mod_info->prot,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED;
}
static inline bool check_split_result(struct line_content *mod_last_line,
struct line_content *mod_first_line,
struct line_content *restored_last_line,
struct line_content *restored_first_line)
{
/* Make sure vmas at the boundaries are changing */
return strcmp(mod_last_line->text, restored_last_line->text) != 0 &&
strcmp(mod_first_line->text, restored_first_line->text) != 0;
}
static inline bool shrink_vma(FIXTURE_DATA(proc_maps_race) *self)
{
return mremap(self->mod_info->addr, self->page_size * 3,
self->page_size, 0) != MAP_FAILED;
}
static inline bool expand_vma(FIXTURE_DATA(proc_maps_race) *self)
{
return mremap(self->mod_info->addr, self->page_size,
self->page_size * 3, 0) != MAP_FAILED;
}
static inline bool check_shrink_result(struct line_content *mod_last_line,
struct line_content *mod_first_line,
struct line_content *restored_last_line,
struct line_content *restored_first_line)
{
/* Make sure only the last vma of the first page is changing */
return strcmp(mod_last_line->text, restored_last_line->text) != 0 &&
strcmp(mod_first_line->text, restored_first_line->text) == 0;
}
static inline bool remap_vma(FIXTURE_DATA(proc_maps_race) *self)
{
/*
* Remap the last page of the next vma into the middle of the vma.
* This splits the current vma and the first and middle parts (the
* parts at lower addresses) become the last vma objserved in the
* first page and the first vma observed in the last page.
*/
return mremap(self->mod_info->next_addr + self->page_size * 2, self->page_size,
self->page_size, MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP,
self->mod_info->addr + self->page_size) != MAP_FAILED;
}
static inline bool patch_vma(FIXTURE_DATA(proc_maps_race) *self)
{
return mprotect(self->mod_info->addr + self->page_size, self->page_size,
self->mod_info->prot) == 0;
}
static inline bool check_remap_result(struct line_content *mod_last_line,
struct line_content *mod_first_line,
struct line_content *restored_last_line,
struct line_content *restored_first_line)
{
/* Make sure vmas at the boundaries are changing */
return strcmp(mod_last_line->text, restored_last_line->text) != 0 &&
strcmp(mod_first_line->text, restored_first_line->text) != 0;
}
FIXTURE_SETUP(proc_maps_race)
{
const char *verbose = getenv("VERBOSE");
const char *duration = getenv("DURATION");
struct vma_modifier_info *mod_info;
pthread_mutexattr_t mutex_attr;
pthread_condattr_t cond_attr;
unsigned long first_map_addr;
unsigned long last_map_addr;
unsigned long duration_sec;
char fname[32];
self->page_size = (unsigned long)sysconf(_SC_PAGESIZE);
self->verbose = verbose && !strncmp(verbose, "1", 1);
self->maps_file = variant->maps_file;
duration_sec = duration ? atol(duration) : 0;
self->duration_sec = duration_sec ? duration_sec : 5UL;
/*
* Have to map enough vmas for /proc/pid/maps to contain more than one
* page worth of vmas. Assume at least 32 bytes per line in maps output
*/
self->vma_count = self->page_size / 32 + 1;
self->shared_mem_size = sizeof(struct vma_modifier_info) + self->vma_count * sizeof(void *);
/* map shared memory for communication with the child process */
self->mod_info = (struct vma_modifier_info *)mmap(NULL, self->shared_mem_size,
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(self->mod_info, MAP_FAILED);
mod_info = self->mod_info;
/* Initialize shared members */
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
ASSERT_EQ(pthread_mutex_init(&mod_info->sync_lock, &mutex_attr), 0);
pthread_condattr_init(&cond_attr);
pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
ASSERT_EQ(pthread_cond_init(&mod_info->sync_cond, &cond_attr), 0);
mod_info->vma_count = self->vma_count;
mod_info->curr_state = INIT;
mod_info->exit = false;
self->pid = fork();
if (!self->pid) {
/* Child process modifying the address space */
int prot = PROT_READ | PROT_WRITE;
int i;
for (i = 0; i < mod_info->vma_count; i++) {
mod_info->child_mapped_addr[i] = mmap(NULL, self->page_size * 3, prot,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(mod_info->child_mapped_addr[i], MAP_FAILED);
/* change protection in adjacent maps to prevent merging */
prot ^= PROT_WRITE;
}
signal_state(mod_info, CHILD_READY);
wait_for_state(mod_info, PARENT_READY);
while (true) {
signal_state(mod_info, SETUP_READY);
wait_for_state(mod_info, SETUP_MODIFY_MAPS);
if (mod_info->exit)
break;
ASSERT_TRUE(mod_info->vma_modify(self));
signal_state(mod_info, SETUP_MAPS_MODIFIED);
wait_for_state(mod_info, SETUP_RESTORE_MAPS);
ASSERT_TRUE(mod_info->vma_restore(self));
signal_state(mod_info, SETUP_MAPS_RESTORED);
wait_for_state(mod_info, TEST_READY);
while (mod_info->curr_state != TEST_DONE) {
ASSERT_TRUE(mod_info->vma_modify(self));
ASSERT_TRUE(mod_info->vma_restore(self));
}
}
for (i = 0; i < mod_info->vma_count; i++)
munmap(mod_info->child_mapped_addr[i], self->page_size * 3);
exit(0);
}
switch (self->maps_file) {
case MAPS:
sprintf(fname, "/proc/%d/maps", self->pid);
break;
case SMAPS:
sprintf(fname, "/proc/%d/smaps", self->pid);
break;
default:
ksft_exit_fail();
}
self->maps_fd = open(fname, O_RDONLY);
ASSERT_NE(self->maps_fd, -1);
/* Wait for the child to map the VMAs */
wait_for_state(mod_info, CHILD_READY);
/* Read first two pages */
self->page1.data = malloc(self->page_size);
ASSERT_NE(self->page1.data, NULL);
self->page2.data = malloc(self->page_size);
ASSERT_NE(self->page2.data, NULL);
first_map_addr = (unsigned long)mod_info->child_mapped_addr[0];
last_map_addr = (unsigned long)mod_info->child_mapped_addr[mod_info->vma_count - 1];
self->skip_pages = locate_containing_page(self,
min(first_map_addr, last_map_addr),
self->page_size * 3);
ASSERT_NE(self->skip_pages, -1);
ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line));
/*
* Find the addresses corresponding to the last line in the first page
* and the first line in the last page.
*/
mod_info->addr = NULL;
mod_info->next_addr = NULL;
for (int i = 0; i < mod_info->vma_count; i++) {
if (mod_info->child_mapped_addr[i] == (void *)self->last_line.start_addr) {
mod_info->addr = mod_info->child_mapped_addr[i];
mod_info->prot = PROT_READ;
/* Even VMAs have write permission */
if ((i % 2) == 0)
mod_info->prot |= PROT_WRITE;
} else if (mod_info->child_mapped_addr[i] == (void *)self->first_line.start_addr) {
mod_info->next_addr = mod_info->child_mapped_addr[i];
}
if (mod_info->addr && mod_info->next_addr)
break;
}
ASSERT_TRUE(mod_info->addr && mod_info->next_addr);
signal_state(mod_info, PARENT_READY);
}
FIXTURE_TEARDOWN(proc_maps_race)
{
int status;
stop_vma_modifier(self->mod_info);
free(self->page2.data);
free(self->page1.data);
for (int i = 0; i < self->vma_count; i++)
munmap(self->mod_info->child_mapped_addr[i], self->page_size);
close(self->maps_fd);
waitpid(self->pid, &status, 0);
munmap(self->mod_info, self->shared_mem_size);
}
TEST_F(proc_maps_race, test_maps_tearing_from_split)
{
struct vma_modifier_info *mod_info = self->mod_info;
struct line_content split_last_line;
struct line_content split_first_line;
struct line_content restored_last_line;
struct line_content restored_first_line;
wait_for_state(mod_info, SETUP_READY);
/* re-read the file to avoid using stale data from previous test */
ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line));
mod_info->vma_modify = split_vma;
mod_info->vma_restore = merge_vma;
mod_info->vma_mod_check = check_split_result;
report_test_start("Tearing from split", self->verbose);
ASSERT_TRUE(capture_mod_pattern(self, &split_last_line, &split_first_line,
&restored_last_line, &restored_first_line));
/* Now start concurrent modifications for self->duration_sec */
signal_state(mod_info, TEST_READY);
struct line_content new_last_line;
struct line_content new_first_line;
struct timespec start_ts, end_ts;
clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts);
start_test_loop(&start_ts, self->verbose);
do {
bool last_line_changed;
bool first_line_changed;
unsigned long vma_start;
unsigned long vma_end;
ASSERT_TRUE(read_boundary_lines(self, &new_last_line, &new_first_line));
/* Check if we read vmas after split */
if (!strcmp(new_last_line.text, split_last_line.text)) {
/*
* The vmas should be consistent with split results,
* however if vma was concurrently restored after a
* split, it can be reported twice (first the original
* split one, then the same vma but extended after the
* merge) because we found it as the next vma again.
* In that case new first line will be the same as the
* last restored line.
*/
ASSERT_FALSE(print_boundaries_on(
strcmp(new_first_line.text, split_first_line.text) &&
strcmp(new_first_line.text, restored_last_line.text),
"Split result invalid", self));
} else {
/* The vmas should be consistent with merge results */
ASSERT_FALSE(print_boundaries_on(
strcmp(new_last_line.text, restored_last_line.text),
"Merge result invalid", self));
ASSERT_FALSE(print_boundaries_on(
strcmp(new_first_line.text, restored_first_line.text),
"Merge result invalid", self));
}
/*
* First and last lines should change in unison. If the last
* line changed then the first line should change as well and
* vice versa.
*/
last_line_changed = strcmp(new_last_line.text, self->last_line.text) != 0;
first_line_changed = strcmp(new_first_line.text, self->first_line.text) != 0;
ASSERT_EQ(last_line_changed, first_line_changed);
if (self->maps_file == MAPS) {
/* Check if PROCMAP_QUERY ioclt() finds the right VMA */
ASSERT_TRUE(query_addr_at(self->maps_fd, mod_info->addr + self->page_size,
&vma_start, &vma_end));
/*
* The vma at the split address can be either the same as
* original one (if read before the split) or the same as the
* first line in the second page (if read after the split).
*/
ASSERT_TRUE((vma_start == self->last_line.start_addr &&
vma_end == self->last_line.end_addr) ||
(vma_start == split_first_line.start_addr &&
vma_end == split_first_line.end_addr));
}
clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts);
end_test_iteration(&end_ts, self->verbose);
} while (end_ts.tv_sec - start_ts.tv_sec < self->duration_sec);
end_test_loop(self->verbose);
/* Signal the modifyer thread to stop and wait until it exits */
signal_state(mod_info, TEST_DONE);
}
TEST_F(proc_maps_race, test_maps_tearing_from_resize)
{
struct vma_modifier_info *mod_info = self->mod_info;
struct line_content shrunk_last_line;
struct line_content shrunk_first_line;
struct line_content restored_last_line;
struct line_content restored_first_line;
wait_for_state(mod_info, SETUP_READY);
/* re-read the file to avoid using stale data from previous test */
ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line));
mod_info->vma_modify = shrink_vma;
mod_info->vma_restore = expand_vma;
mod_info->vma_mod_check = check_shrink_result;
report_test_start("Tearing from resize", self->verbose);
ASSERT_TRUE(capture_mod_pattern(self, &shrunk_last_line, &shrunk_first_line,
&restored_last_line, &restored_first_line));
/* Now start concurrent modifications for self->duration_sec */
signal_state(mod_info, TEST_READY);
struct line_content new_last_line;
struct line_content new_first_line;
struct timespec start_ts, end_ts;
clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts);
start_test_loop(&start_ts, self->verbose);
do {
unsigned long vma_start;
unsigned long vma_end;
ASSERT_TRUE(read_boundary_lines(self, &new_last_line, &new_first_line));
/* Check if we read vmas after shrinking it */
if (!strcmp(new_last_line.text, shrunk_last_line.text)) {
/*
* The vmas should be consistent with shrunk results,
* however if the vma was concurrently restored, it
* can be reported twice (first as shrunk one, then
* as restored one) because we found it as the next vma
* again. In that case new first line will be the same
* as the last restored line.
*/
ASSERT_FALSE(print_boundaries_on(
strcmp(new_first_line.text, shrunk_first_line.text) &&
strcmp(new_first_line.text, restored_last_line.text),
"Shrink result invalid", self));
} else {
/* The vmas should be consistent with the original/resored state */
ASSERT_FALSE(print_boundaries_on(
strcmp(new_last_line.text, restored_last_line.text),
"Expand result invalid", self));
ASSERT_FALSE(print_boundaries_on(
strcmp(new_first_line.text, restored_first_line.text),
"Expand result invalid", self));
}
if (self->maps_file == MAPS) {
/* Check if PROCMAP_QUERY ioclt() finds the right VMA */
ASSERT_TRUE(query_addr_at(self->maps_fd, mod_info->addr,
&vma_start, &vma_end));
/*
* The vma should stay at the same address and have either the
* original size of 3 pages or 1 page if read after shrinking.
*/
ASSERT_TRUE(vma_start == self->last_line.start_addr &&
(vma_end - vma_start == self->page_size * 3 ||
vma_end - vma_start == self->page_size));
}
clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts);
end_test_iteration(&end_ts, self->verbose);
} while (end_ts.tv_sec - start_ts.tv_sec < self->duration_sec);
end_test_loop(self->verbose);
/* Signal the modifyer thread to stop and wait until it exits */
signal_state(mod_info, TEST_DONE);
}
TEST_F(proc_maps_race, test_maps_tearing_from_remap)
{
struct vma_modifier_info *mod_info = self->mod_info;
struct line_content remapped_last_line;
struct line_content remapped_first_line;
struct line_content restored_last_line;
struct line_content restored_first_line;
wait_for_state(mod_info, SETUP_READY);
/* re-read the file to avoid using stale data from previous test */
ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line));
mod_info->vma_modify = remap_vma;
mod_info->vma_restore = patch_vma;
mod_info->vma_mod_check = check_remap_result;
report_test_start("Tearing from remap", self->verbose);
ASSERT_TRUE(capture_mod_pattern(self, &remapped_last_line, &remapped_first_line,
&restored_last_line, &restored_first_line));
/* Now start concurrent modifications for self->duration_sec */
signal_state(mod_info, TEST_READY);
struct line_content new_last_line;
struct line_content new_first_line;
struct timespec start_ts, end_ts;
clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts);
start_test_loop(&start_ts, self->verbose);
do {
unsigned long vma_start;
unsigned long vma_end;
ASSERT_TRUE(read_boundary_lines(self, &new_last_line, &new_first_line));
/* Check if we read vmas after remapping it */
if (!strcmp(new_last_line.text, remapped_last_line.text)) {
/*
* The vmas should be consistent with remap results,
* however if the vma was concurrently restored, it
* can be reported twice (first as split one, then
* as restored one) because we found it as the next vma
* again. In that case new first line will be the same
* as the last restored line.
*/
ASSERT_FALSE(print_boundaries_on(
strcmp(new_first_line.text, remapped_first_line.text) &&
strcmp(new_first_line.text, restored_last_line.text),
"Remap result invalid", self));
} else {
/* The vmas should be consistent with the original/resored state */
ASSERT_FALSE(print_boundaries_on(
strcmp(new_last_line.text, restored_last_line.text),
"Remap restore result invalid", self));
ASSERT_FALSE(print_boundaries_on(
strcmp(new_first_line.text, restored_first_line.text),
"Remap restore result invalid", self));
}
if (self->maps_file == MAPS) {
/* Check if PROCMAP_QUERY ioclt() finds the right VMA */
ASSERT_TRUE(query_addr_at(self->maps_fd, mod_info->addr + self->page_size,
&vma_start, &vma_end));
/*
* The vma should either stay at the same address and have the
* original size of 3 pages or we should find the remapped vma
* at the remap destination address with size of 1 page.
*/
ASSERT_TRUE((vma_start == self->last_line.start_addr &&
vma_end - vma_start == self->page_size * 3) ||
(vma_start == self->last_line.start_addr + self->page_size &&
vma_end - vma_start == self->page_size));
}
clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts);
end_test_iteration(&end_ts, self->verbose);
} while (end_ts.tv_sec - start_ts.tv_sec < self->duration_sec);
end_test_loop(self->verbose);
/* Signal the modifyer thread to stop and wait until it exits */
signal_state(mod_info, TEST_DONE);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,48 @@
/*
* Copyright © 2020 Alexey Gladkov <gladkov.alexey@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(void)
{
struct stat proc_st1, proc_st2;
char procbuff[] = "/tmp/proc.XXXXXX/meminfo";
char procdir1[] = "/tmp/proc.XXXXXX";
char procdir2[] = "/tmp/proc.XXXXXX";
assert(mkdtemp(procdir1) != NULL);
assert(mkdtemp(procdir2) != NULL);
assert(!mount("proc", procdir1, "proc", 0, "hidepid=1"));
assert(!mount("proc", procdir2, "proc", 0, "hidepid=2"));
snprintf(procbuff, sizeof(procbuff), "%s/meminfo", procdir1);
assert(!stat(procbuff, &proc_st1));
snprintf(procbuff, sizeof(procbuff), "%s/meminfo", procdir2);
assert(!stat(procbuff, &proc_st2));
umount(procdir1);
umount(procdir2);
assert(proc_st1.st_dev != proc_st2.st_dev);
return 0;
}
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2025 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#undef _GNU_SOURCE
#define _GNU_SOURCE
#undef NDEBUG
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
/*
* Test that lseek("/proc/net/dev/", 0, SEEK_SET)
* a) works,
* b) does what you think it does.
*/
int main(void)
{
/* /proc/net/dev output is deterministic in fresh netns only. */
if (unshare(CLONE_NEWNET) == -1) {
if (errno == ENOSYS || errno == EPERM) {
return 4;
}
return 1;
}
const int fd = open("/proc/net/dev", O_RDONLY);
assert(fd >= 0);
char buf1[4096];
const ssize_t rv1 = read(fd, buf1, sizeof(buf1));
/*
* Not "<=", this file can't be empty:
* there is header, "lo" interface with some zeroes.
*/
assert(0 < rv1);
assert(rv1 <= sizeof(buf1));
/* Believe it or not, this line broke one day. */
assert(lseek(fd, 0, SEEK_SET) == 0);
char buf2[4096];
const ssize_t rv2 = read(fd, buf2, sizeof(buf2));
/* Not "<=", see above. */
assert(0 < rv2);
assert(rv2 <= sizeof(buf2));
/* Test that lseek rewinds to the beginning of the file. */
assert(rv1 == rv2);
assert(memcmp(buf1, buf2, rv1) == 0);
/* Contents of the file is not validated: this test is about lseek(). */
return 0;
}
@@ -0,0 +1,592 @@
/*
* Copyright (c) 2019 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Fork and exec tiny 1 page executable which precisely controls its VM.
* Test /proc/$PID/maps
* Test /proc/$PID/smaps
* Test /proc/$PID/smaps_rollup
* Test /proc/$PID/statm
*
* FIXME require CONFIG_TMPFS which can be disabled
* FIXME test other values from "smaps"
* FIXME support other archs
*/
#undef NDEBUG
#include <assert.h>
#include <errno.h>
#include <sched.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include <linux/kdev_t.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <linux/fs.h>
#ifndef __maybe_unused
#define __maybe_unused __attribute__((__unused__))
#endif
#include "kselftest.h"
static inline long sys_execveat(int dirfd, const char *pathname, char **argv, char **envp, int flags)
{
return syscall(SYS_execveat, dirfd, pathname, argv, envp, flags);
}
static void make_private_tmp(void)
{
if (unshare(CLONE_NEWNS) == -1) {
if (errno == ENOSYS || errno == EPERM) {
exit(4);
}
exit(1);
}
if (mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL) == -1) {
exit(1);
}
if (mount(NULL, "/tmp", "tmpfs", 0, NULL) == -1) {
exit(1);
}
}
static pid_t pid = -1;
static void ate(void)
{
if (pid > 0) {
kill(pid, SIGTERM);
}
}
struct elf64_hdr {
uint8_t e_ident[16];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint64_t e_entry;
uint64_t e_phoff;
uint64_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
};
struct elf64_phdr {
uint32_t p_type;
uint32_t p_flags;
uint64_t p_offset;
uint64_t p_vaddr;
uint64_t p_paddr;
uint64_t p_filesz;
uint64_t p_memsz;
uint64_t p_align;
};
#ifdef __x86_64__
#define PAGE_SIZE 4096
#define VADDR (1UL << 32)
#define MAPS_OFFSET 73
#define syscall 0x0f, 0x05
#define mov_rdi(x) \
0x48, 0xbf, \
(x)&0xff, ((x)>>8)&0xff, ((x)>>16)&0xff, ((x)>>24)&0xff, \
((x)>>32)&0xff, ((x)>>40)&0xff, ((x)>>48)&0xff, ((x)>>56)&0xff
#define mov_rsi(x) \
0x48, 0xbe, \
(x)&0xff, ((x)>>8)&0xff, ((x)>>16)&0xff, ((x)>>24)&0xff, \
((x)>>32)&0xff, ((x)>>40)&0xff, ((x)>>48)&0xff, ((x)>>56)&0xff
#define mov_eax(x) \
0xb8, (x)&0xff, ((x)>>8)&0xff, ((x)>>16)&0xff, ((x)>>24)&0xff
static const uint8_t payload[] = {
/* Casually unmap stack, vDSO and everything else. */
/* munmap */
mov_rdi(VADDR + 4096),
mov_rsi((1ULL << 47) - 4096 - VADDR - 4096),
mov_eax(11),
syscall,
/* Ping parent. */
/* write(0, &c, 1); */
0x31, 0xff, /* xor edi, edi */
0x48, 0x8d, 0x35, 0x00, 0x00, 0x00, 0x00, /* lea rsi, [rip] */
0xba, 0x01, 0x00, 0x00, 0x00, /* mov edx, 1 */
mov_eax(1),
syscall,
/* 1: pause(); */
mov_eax(34),
syscall,
0xeb, 0xf7, /* jmp 1b */
};
static int make_exe(const uint8_t *payload, size_t len)
{
struct elf64_hdr h;
struct elf64_phdr ph;
struct iovec iov[3] = {
{&h, sizeof(struct elf64_hdr)},
{&ph, sizeof(struct elf64_phdr)},
{(void *)payload, len},
};
int fd, fd1;
char buf[64];
memset(&h, 0, sizeof(h));
h.e_ident[0] = 0x7f;
h.e_ident[1] = 'E';
h.e_ident[2] = 'L';
h.e_ident[3] = 'F';
h.e_ident[4] = 2;
h.e_ident[5] = 1;
h.e_ident[6] = 1;
h.e_ident[7] = 0;
h.e_type = 2;
h.e_machine = 0x3e;
h.e_version = 1;
h.e_entry = VADDR + sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr);
h.e_phoff = sizeof(struct elf64_hdr);
h.e_shoff = 0;
h.e_flags = 0;
h.e_ehsize = sizeof(struct elf64_hdr);
h.e_phentsize = sizeof(struct elf64_phdr);
h.e_phnum = 1;
h.e_shentsize = 0;
h.e_shnum = 0;
h.e_shstrndx = 0;
memset(&ph, 0, sizeof(ph));
ph.p_type = 1;
ph.p_flags = (1<<2)|1;
ph.p_offset = 0;
ph.p_vaddr = VADDR;
ph.p_paddr = 0;
ph.p_filesz = sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr) + len;
ph.p_memsz = sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr) + len;
ph.p_align = 4096;
fd = openat(AT_FDCWD, "/tmp", O_WRONLY|O_EXCL|O_TMPFILE, 0700);
if (fd == -1) {
exit(1);
}
if (writev(fd, iov, 3) != sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr) + len) {
exit(1);
}
/* Avoid ETXTBSY on exec. */
snprintf(buf, sizeof(buf), "/proc/self/fd/%u", fd);
fd1 = open(buf, O_RDONLY|O_CLOEXEC);
close(fd);
return fd1;
}
#endif
/*
* 0: vsyscall VMA doesn't exist vsyscall=none
* 1: vsyscall VMA is --xp vsyscall=xonly
* 2: vsyscall VMA is r-xp vsyscall=emulate
*/
static volatile int g_vsyscall;
static const char *str_vsyscall __maybe_unused;
static const char str_vsyscall_0[] __maybe_unused = "";
static const char str_vsyscall_1[] __maybe_unused =
"ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n";
static const char str_vsyscall_2[] __maybe_unused =
"ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]\n";
#ifdef __x86_64__
static void sigaction_SIGSEGV(int _, siginfo_t *__, void *___)
{
_exit(g_vsyscall);
}
/*
* vsyscall page can't be unmapped, probe it directly.
*/
static void vsyscall(void)
{
pid_t pid;
int wstatus;
pid = fork();
if (pid < 0) {
fprintf(stderr, "fork, errno %d\n", errno);
exit(1);
}
if (pid == 0) {
struct rlimit rlim = {0, 0};
(void)setrlimit(RLIMIT_CORE, &rlim);
/* Hide "segfault at ffffffffff600000" messages. */
struct sigaction act;
memset(&act, 0, sizeof(struct sigaction));
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = sigaction_SIGSEGV;
(void)sigaction(SIGSEGV, &act, NULL);
g_vsyscall = 0;
/* gettimeofday(NULL, NULL); */
uint64_t rax = 0xffffffffff600000;
asm volatile (
"call *%[rax]"
: [rax] "+a" (rax)
: "D" (NULL), "S" (NULL)
: "rcx", "r11"
);
g_vsyscall = 1;
*(volatile int *)0xffffffffff600000UL;
g_vsyscall = 2;
exit(g_vsyscall);
}
waitpid(pid, &wstatus, 0);
if (WIFEXITED(wstatus)) {
g_vsyscall = WEXITSTATUS(wstatus);
} else {
fprintf(stderr, "error: wstatus %08x\n", wstatus);
exit(1);
}
}
int main(void)
{
int pipefd[2];
int exec_fd;
vsyscall();
switch (g_vsyscall) {
case 0:
str_vsyscall = str_vsyscall_0;
break;
case 1:
str_vsyscall = str_vsyscall_1;
break;
case 2:
str_vsyscall = str_vsyscall_2;
break;
default:
abort();
}
atexit(ate);
make_private_tmp();
/* Reserve fd 0 for 1-byte pipe ping from child. */
close(0);
if (open("/", O_RDONLY|O_DIRECTORY|O_PATH) != 0) {
return 1;
}
exec_fd = make_exe(payload, sizeof(payload));
if (pipe(pipefd) == -1) {
return 1;
}
if (dup2(pipefd[1], 0) != 0) {
return 1;
}
pid = fork();
if (pid == -1) {
return 1;
}
if (pid == 0) {
sys_execveat(exec_fd, "", NULL, NULL, AT_EMPTY_PATH);
return 1;
}
char _;
if (read(pipefd[0], &_, 1) != 1) {
return 1;
}
struct stat st;
if (fstat(exec_fd, &st) == -1) {
return 1;
}
/* Generate "head -n1 /proc/$PID/maps" */
char buf0[256];
memset(buf0, ' ', sizeof(buf0));
int len = snprintf(buf0, sizeof(buf0),
"%08lx-%08lx r-xp 00000000 %02lx:%02lx %llu",
VADDR, VADDR + PAGE_SIZE,
MAJOR(st.st_dev), MINOR(st.st_dev),
(unsigned long long)st.st_ino);
buf0[len] = ' ';
snprintf(buf0 + MAPS_OFFSET, sizeof(buf0) - MAPS_OFFSET,
"/tmp/#%llu (deleted)\n", (unsigned long long)st.st_ino);
/* Test /proc/$PID/maps */
{
const size_t len = strlen(buf0) + strlen(str_vsyscall);
char buf[256];
ssize_t rv;
int fd;
snprintf(buf, sizeof(buf), "/proc/%u/maps", pid);
fd = open(buf, O_RDONLY);
if (fd == -1) {
return 1;
}
rv = read(fd, buf, sizeof(buf));
assert(rv == len);
assert(memcmp(buf, buf0, strlen(buf0)) == 0);
if (g_vsyscall > 0) {
assert(memcmp(buf + strlen(buf0), str_vsyscall, strlen(str_vsyscall)) == 0);
}
}
/* Test /proc/$PID/smaps */
{
char buf[4096];
ssize_t rv;
int fd;
snprintf(buf, sizeof(buf), "/proc/%u/smaps", pid);
fd = open(buf, O_RDONLY);
if (fd == -1) {
return 1;
}
rv = read(fd, buf, sizeof(buf));
assert(0 <= rv && rv <= sizeof(buf));
assert(rv >= strlen(buf0));
assert(memcmp(buf, buf0, strlen(buf0)) == 0);
#define RSS1 "Rss: 4 kB\n"
#define RSS2 "Rss: 0 kB\n"
#define PSS1 "Pss: 4 kB\n"
#define PSS2 "Pss: 0 kB\n"
assert(memmem(buf, rv, RSS1, strlen(RSS1)) ||
memmem(buf, rv, RSS2, strlen(RSS2)));
assert(memmem(buf, rv, PSS1, strlen(PSS1)) ||
memmem(buf, rv, PSS2, strlen(PSS2)));
static const char *S[] = {
"Size: 4 kB\n",
"KernelPageSize: 4 kB\n",
"MMUPageSize: 4 kB\n",
"Anonymous: 0 kB\n",
"AnonHugePages: 0 kB\n",
"Shared_Hugetlb: 0 kB\n",
"Private_Hugetlb: 0 kB\n",
"Locked: 0 kB\n",
};
int i;
for (i = 0; i < ARRAY_SIZE(S); i++) {
assert(memmem(buf, rv, S[i], strlen(S[i])));
}
if (g_vsyscall > 0) {
assert(memmem(buf, rv, str_vsyscall, strlen(str_vsyscall)));
}
}
/* Test /proc/$PID/smaps_rollup */
{
char bufr[256];
memset(bufr, ' ', sizeof(bufr));
len = snprintf(bufr, sizeof(bufr),
"%08lx-%08lx ---p 00000000 00:00 0",
VADDR, VADDR + PAGE_SIZE);
bufr[len] = ' ';
snprintf(bufr + MAPS_OFFSET, sizeof(bufr) - MAPS_OFFSET,
"[rollup]\n");
char buf[1024];
ssize_t rv;
int fd;
snprintf(buf, sizeof(buf), "/proc/%u/smaps_rollup", pid);
fd = open(buf, O_RDONLY);
if (fd == -1) {
return 1;
}
rv = read(fd, buf, sizeof(buf));
assert(0 <= rv && rv <= sizeof(buf));
assert(rv >= strlen(bufr));
assert(memcmp(buf, bufr, strlen(bufr)) == 0);
assert(memmem(buf, rv, RSS1, strlen(RSS1)) ||
memmem(buf, rv, RSS2, strlen(RSS2)));
assert(memmem(buf, rv, PSS1, strlen(PSS1)) ||
memmem(buf, rv, PSS2, strlen(PSS2)));
static const char *S[] = {
"Anonymous: 0 kB\n",
"AnonHugePages: 0 kB\n",
"Shared_Hugetlb: 0 kB\n",
"Private_Hugetlb: 0 kB\n",
"Locked: 0 kB\n",
};
int i;
for (i = 0; i < ARRAY_SIZE(S); i++) {
assert(memmem(buf, rv, S[i], strlen(S[i])));
}
}
/* Test /proc/$PID/statm */
{
char buf[64];
ssize_t rv;
int fd;
snprintf(buf, sizeof(buf), "/proc/%u/statm", pid);
fd = open(buf, O_RDONLY);
if (fd == -1) {
return 1;
}
rv = read(fd, buf, sizeof(buf));
assert(rv == 7 * 2);
assert(buf[0] == '1'); /* ->total_vm */
assert(buf[1] == ' ');
assert(buf[2] == '0' || buf[2] == '1'); /* rss */
assert(buf[3] == ' ');
assert(buf[4] == '0' || buf[2] == '1'); /* file rss */
assert(buf[5] == ' ');
assert(buf[6] == '1'); /* ELF executable segments */
assert(buf[7] == ' ');
assert(buf[8] == '0');
assert(buf[9] == ' ');
assert(buf[10] == '0'); /* ->data_vm + ->stack_vm */
assert(buf[11] == ' ');
assert(buf[12] == '0');
assert(buf[13] == '\n');
}
/* Test PROCMAP_QUERY ioctl() for /proc/$PID/maps */
{
char path_buf[256], exp_path_buf[256];
struct procmap_query q;
int fd, err;
snprintf(path_buf, sizeof(path_buf), "/proc/%u/maps", pid);
fd = open(path_buf, O_RDONLY);
if (fd == -1)
return 1;
/* CASE 1: exact MATCH at VADDR */
memset(&q, 0, sizeof(q));
q.size = sizeof(q);
q.query_addr = VADDR;
q.query_flags = 0;
q.vma_name_addr = (__u64)(unsigned long)path_buf;
q.vma_name_size = sizeof(path_buf);
err = ioctl(fd, PROCMAP_QUERY, &q);
assert(err == 0);
assert(q.query_addr == VADDR);
assert(q.query_flags == 0);
assert(q.vma_flags == (PROCMAP_QUERY_VMA_READABLE | PROCMAP_QUERY_VMA_EXECUTABLE));
assert(q.vma_start == VADDR);
assert(q.vma_end == VADDR + PAGE_SIZE);
assert(q.vma_page_size == PAGE_SIZE);
assert(q.vma_offset == 0);
assert(q.inode == st.st_ino);
assert(q.dev_major == MAJOR(st.st_dev));
assert(q.dev_minor == MINOR(st.st_dev));
snprintf(exp_path_buf, sizeof(exp_path_buf),
"/tmp/#%llu (deleted)", (unsigned long long)st.st_ino);
assert(q.vma_name_size == strlen(exp_path_buf) + 1);
assert(strcmp(path_buf, exp_path_buf) == 0);
/* CASE 2: NO MATCH at VADDR-1 */
memset(&q, 0, sizeof(q));
q.size = sizeof(q);
q.query_addr = VADDR - 1;
q.query_flags = 0; /* exact match */
err = ioctl(fd, PROCMAP_QUERY, &q);
err = err < 0 ? -errno : 0;
assert(err == -ENOENT);
/* CASE 3: MATCH COVERING_OR_NEXT_VMA at VADDR - 1 */
memset(&q, 0, sizeof(q));
q.size = sizeof(q);
q.query_addr = VADDR - 1;
q.query_flags = PROCMAP_QUERY_COVERING_OR_NEXT_VMA;
err = ioctl(fd, PROCMAP_QUERY, &q);
assert(err == 0);
assert(q.query_addr == VADDR - 1);
assert(q.query_flags == PROCMAP_QUERY_COVERING_OR_NEXT_VMA);
assert(q.vma_start == VADDR);
assert(q.vma_end == VADDR + PAGE_SIZE);
/* CASE 4: NO MATCH at VADDR + PAGE_SIZE */
memset(&q, 0, sizeof(q));
q.size = sizeof(q);
q.query_addr = VADDR + PAGE_SIZE; /* point right after the VMA */
q.query_flags = PROCMAP_QUERY_COVERING_OR_NEXT_VMA;
err = ioctl(fd, PROCMAP_QUERY, &q);
err = err < 0 ? -errno : 0;
assert(err == -ENOENT);
/* CASE 5: NO MATCH WRITABLE at VADDR */
memset(&q, 0, sizeof(q));
q.size = sizeof(q);
q.query_addr = VADDR;
q.query_flags = PROCMAP_QUERY_VMA_WRITABLE;
err = ioctl(fd, PROCMAP_QUERY, &q);
err = err < 0 ? -errno : 0;
assert(err == -ENOENT);
}
return 0;
}
#else
int main(void)
{
return 4;
}
#endif
@@ -0,0 +1,211 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Author: Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2025 SUSE LLC.
*/
#include <assert.h>
#include <errno.h>
#include <sched.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include "kselftest_harness.h"
#define ASSERT_ERRNO(expected, _t, seen) \
__EXPECT(expected, #expected, \
({__typeof__(seen) _tmp_seen = (seen); \
_tmp_seen >= 0 ? _tmp_seen : -errno; }), #seen, _t, 1)
#define ASSERT_ERRNO_EQ(expected, seen) \
ASSERT_ERRNO(expected, ==, seen)
#define ASSERT_SUCCESS(seen) \
ASSERT_ERRNO(0, <=, seen)
static int touch(char *path)
{
int fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC, 0644);
if (fd < 0)
return -1;
return close(fd);
}
FIXTURE(ns)
{
int host_mntns, host_pidns;
int dummy_pidns;
};
FIXTURE_SETUP(ns)
{
/* Stash the old mntns. */
self->host_mntns = open("/proc/self/ns/mnt", O_RDONLY|O_CLOEXEC);
ASSERT_SUCCESS(self->host_mntns);
/* Create a new mount namespace and make it private. */
ASSERT_SUCCESS(unshare(CLONE_NEWNS));
ASSERT_SUCCESS(mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL));
/*
* Create a proper tmpfs that we can use and will disappear once we
* leave this mntns.
*/
ASSERT_SUCCESS(mount("tmpfs", "/tmp", "tmpfs", 0, NULL));
/*
* Create a pidns we can use for later tests. We need to fork off a
* child so that we get a usable nsfd that we can bind-mount and open.
*/
ASSERT_SUCCESS(mkdir("/tmp/dummy", 0755));
ASSERT_SUCCESS(touch("/tmp/dummy/pidns"));
ASSERT_SUCCESS(mkdir("/tmp/dummy/proc", 0755));
self->host_pidns = open("/proc/self/ns/pid", O_RDONLY|O_CLOEXEC);
ASSERT_SUCCESS(self->host_pidns);
ASSERT_SUCCESS(unshare(CLONE_NEWPID));
pid_t pid = fork();
ASSERT_SUCCESS(pid);
if (!pid) {
prctl(PR_SET_PDEATHSIG, SIGKILL);
ASSERT_SUCCESS(mount("/proc/self/ns/pid", "/tmp/dummy/pidns", NULL, MS_BIND, NULL));
ASSERT_SUCCESS(mount("proc", "/tmp/dummy/proc", "proc", 0, NULL));
exit(0);
}
int wstatus;
ASSERT_EQ(waitpid(pid, &wstatus, 0), pid);
ASSERT_TRUE(WIFEXITED(wstatus));
ASSERT_EQ(WEXITSTATUS(wstatus), 0);
ASSERT_SUCCESS(setns(self->host_pidns, CLONE_NEWPID));
self->dummy_pidns = open("/tmp/dummy/pidns", O_RDONLY|O_CLOEXEC);
ASSERT_SUCCESS(self->dummy_pidns);
}
FIXTURE_TEARDOWN(ns)
{
ASSERT_SUCCESS(setns(self->host_mntns, CLONE_NEWNS));
ASSERT_SUCCESS(close(self->host_mntns));
ASSERT_SUCCESS(close(self->host_pidns));
ASSERT_SUCCESS(close(self->dummy_pidns));
}
TEST_F(ns, pidns_mount_string_path)
{
ASSERT_SUCCESS(mkdir("/tmp/proc-host", 0755));
ASSERT_SUCCESS(mount("proc", "/tmp/proc-host", "proc", 0, "pidns=/proc/self/ns/pid"));
ASSERT_SUCCESS(access("/tmp/proc-host/self/", X_OK));
ASSERT_SUCCESS(mkdir("/tmp/proc-dummy", 0755));
ASSERT_SUCCESS(mount("proc", "/tmp/proc-dummy", "proc", 0, "pidns=/tmp/dummy/pidns"));
ASSERT_ERRNO_EQ(-ENOENT, access("/tmp/proc-dummy/1/", X_OK));
ASSERT_ERRNO_EQ(-ENOENT, access("/tmp/proc-dummy/self/", X_OK));
}
TEST_F(ns, pidns_fsconfig_string_path)
{
int fsfd = fsopen("proc", FSOPEN_CLOEXEC);
ASSERT_SUCCESS(fsfd);
ASSERT_SUCCESS(fsconfig(fsfd, FSCONFIG_SET_STRING, "pidns", "/tmp/dummy/pidns", 0));
ASSERT_SUCCESS(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0));
int mountfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0);
ASSERT_SUCCESS(mountfd);
ASSERT_ERRNO_EQ(-ENOENT, faccessat(mountfd, "1/", X_OK, 0));
ASSERT_ERRNO_EQ(-ENOENT, faccessat(mountfd, "self/", X_OK, 0));
ASSERT_SUCCESS(close(fsfd));
ASSERT_SUCCESS(close(mountfd));
}
TEST_F(ns, pidns_fsconfig_fd)
{
int fsfd = fsopen("proc", FSOPEN_CLOEXEC);
ASSERT_SUCCESS(fsfd);
ASSERT_SUCCESS(fsconfig(fsfd, FSCONFIG_SET_FD, "pidns", NULL, self->dummy_pidns));
ASSERT_SUCCESS(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0));
int mountfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0);
ASSERT_SUCCESS(mountfd);
ASSERT_ERRNO_EQ(-ENOENT, faccessat(mountfd, "1/", X_OK, 0));
ASSERT_ERRNO_EQ(-ENOENT, faccessat(mountfd, "self/", X_OK, 0));
ASSERT_SUCCESS(close(fsfd));
ASSERT_SUCCESS(close(mountfd));
}
TEST_F(ns, pidns_reconfigure_remount)
{
ASSERT_SUCCESS(mkdir("/tmp/proc", 0755));
ASSERT_SUCCESS(mount("proc", "/tmp/proc", "proc", 0, ""));
ASSERT_SUCCESS(access("/tmp/proc/1/", X_OK));
ASSERT_SUCCESS(access("/tmp/proc/self/", X_OK));
ASSERT_ERRNO_EQ(-EBUSY, mount(NULL, "/tmp/proc", NULL, MS_REMOUNT, "pidns=/tmp/dummy/pidns"));
ASSERT_SUCCESS(access("/tmp/proc/1/", X_OK));
ASSERT_SUCCESS(access("/tmp/proc/self/", X_OK));
}
TEST_F(ns, pidns_reconfigure_fsconfig_string_path)
{
int fsfd = fsopen("proc", FSOPEN_CLOEXEC);
ASSERT_SUCCESS(fsfd);
ASSERT_SUCCESS(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0));
int mountfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0);
ASSERT_SUCCESS(mountfd);
ASSERT_SUCCESS(faccessat(mountfd, "1/", X_OK, 0));
ASSERT_SUCCESS(faccessat(mountfd, "self/", X_OK, 0));
ASSERT_ERRNO_EQ(-EBUSY, fsconfig(fsfd, FSCONFIG_SET_STRING, "pidns", "/tmp/dummy/pidns", 0));
ASSERT_SUCCESS(fsconfig(fsfd, FSCONFIG_CMD_RECONFIGURE, NULL, NULL, 0)); /* noop */
ASSERT_SUCCESS(faccessat(mountfd, "1/", X_OK, 0));
ASSERT_SUCCESS(faccessat(mountfd, "self/", X_OK, 0));
ASSERT_SUCCESS(close(fsfd));
ASSERT_SUCCESS(close(mountfd));
}
TEST_F(ns, pidns_reconfigure_fsconfig_fd)
{
int fsfd = fsopen("proc", FSOPEN_CLOEXEC);
ASSERT_SUCCESS(fsfd);
ASSERT_SUCCESS(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0));
int mountfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0);
ASSERT_SUCCESS(mountfd);
ASSERT_SUCCESS(faccessat(mountfd, "1/", X_OK, 0));
ASSERT_SUCCESS(faccessat(mountfd, "self/", X_OK, 0));
ASSERT_ERRNO_EQ(-EBUSY, fsconfig(fsfd, FSCONFIG_SET_FD, "pidns", NULL, self->dummy_pidns));
ASSERT_SUCCESS(fsconfig(fsfd, FSCONFIG_CMD_RECONFIGURE, NULL, NULL, 0)); /* noop */
ASSERT_SUCCESS(faccessat(mountfd, "1/", X_OK, 0));
ASSERT_SUCCESS(faccessat(mountfd, "self/", X_OK, 0));
ASSERT_SUCCESS(close(fsfd));
ASSERT_SUCCESS(close(mountfd));
}
TEST_HARNESS_MAIN
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2024 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* Test that userspace program is not kernel thread. */
#undef NDEBUG
#include <assert.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int fd = open("/proc/self/status", O_RDONLY);
assert(fd >= 0);
char buf[4096];
ssize_t rv = read(fd, buf, sizeof(buf));
assert(0 <= rv && rv < sizeof(buf));
buf[rv] = '\0';
/* This test is very much not kernel thread. */
assert(strstr(buf, "Kthread:\t0\n"));
return 0;
}
@@ -0,0 +1,82 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* Test readlink /proc/self/map_files/... */
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
static void pass(const char *fmt, unsigned long a, unsigned long b)
{
char name[64];
char buf[64];
snprintf(name, sizeof(name), fmt, a, b);
if (readlink(name, buf, sizeof(buf)) == -1)
exit(1);
}
static void fail(const char *fmt, unsigned long a, unsigned long b)
{
char name[64];
char buf[64];
snprintf(name, sizeof(name), fmt, a, b);
if (readlink(name, buf, sizeof(buf)) == -1 && errno == ENOENT)
return;
exit(1);
}
int main(void)
{
const unsigned int PAGE_SIZE = sysconf(_SC_PAGESIZE);
void *p;
int fd;
unsigned long a, b;
fd = open("/dev/zero", O_RDONLY);
if (fd == -1)
return 1;
p = mmap(NULL, PAGE_SIZE, PROT_NONE, MAP_PRIVATE|MAP_FILE, fd, 0);
if (p == MAP_FAILED)
return 1;
a = (unsigned long)p;
b = (unsigned long)p + PAGE_SIZE;
pass("/proc/self/map_files/%lx-%lx", a, b);
fail("/proc/self/map_files/ %lx-%lx", a, b);
fail("/proc/self/map_files/%lx -%lx", a, b);
fail("/proc/self/map_files/%lx- %lx", a, b);
fail("/proc/self/map_files/%lx-%lx ", a, b);
fail("/proc/self/map_files/0%lx-%lx", a, b);
fail("/proc/self/map_files/%lx-0%lx", a, b);
if (sizeof(long) == 4) {
fail("/proc/self/map_files/100000000%lx-%lx", a, b);
fail("/proc/self/map_files/%lx-100000000%lx", a, b);
} else if (sizeof(long) == 8) {
fail("/proc/self/map_files/10000000000000000%lx-%lx", a, b);
fail("/proc/self/map_files/%lx-10000000000000000%lx", a, b);
} else
return 1;
return 0;
}
@@ -0,0 +1,94 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* Test readlink /proc/self/map_files/... with minimum address. */
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
static void pass(const char *fmt, unsigned long a, unsigned long b)
{
char name[64];
char buf[64];
snprintf(name, sizeof(name), fmt, a, b);
if (readlink(name, buf, sizeof(buf)) == -1)
exit(1);
}
static void fail(const char *fmt, unsigned long a, unsigned long b)
{
char name[64];
char buf[64];
snprintf(name, sizeof(name), fmt, a, b);
if (readlink(name, buf, sizeof(buf)) == -1 && errno == ENOENT)
return;
exit(1);
}
int main(void)
{
const int PAGE_SIZE = sysconf(_SC_PAGESIZE);
/*
* va_max must be enough bigger than vm.mmap_min_addr, which is
* 64KB/32KB by default. (depends on CONFIG_LSM_MMAP_MIN_ADDR)
*/
const unsigned long va_max = 1UL << 20;
unsigned long va;
void *p;
int fd;
unsigned long a, b;
fd = open("/dev/zero", O_RDONLY);
if (fd == -1)
return 1;
for (va = 0; va < va_max; va += PAGE_SIZE) {
p = mmap((void *)va, PAGE_SIZE, PROT_NONE, MAP_PRIVATE|MAP_FILE|MAP_FIXED, fd, 0);
if (p == (void *)va)
break;
}
if (va == va_max) {
fprintf(stderr, "error: mmap doesn't like you\n");
return 1;
}
a = (unsigned long)p;
b = (unsigned long)p + PAGE_SIZE;
pass("/proc/self/map_files/%lx-%lx", a, b);
fail("/proc/self/map_files/ %lx-%lx", a, b);
fail("/proc/self/map_files/%lx -%lx", a, b);
fail("/proc/self/map_files/%lx- %lx", a, b);
fail("/proc/self/map_files/%lx-%lx ", a, b);
fail("/proc/self/map_files/0%lx-%lx", a, b);
fail("/proc/self/map_files/%lx-0%lx", a, b);
if (sizeof(long) == 4) {
fail("/proc/self/map_files/100000000%lx-%lx", a, b);
fail("/proc/self/map_files/%lx-100000000%lx", a, b);
} else if (sizeof(long) == 8) {
fail("/proc/self/map_files/10000000000000000%lx-%lx", a, b);
fail("/proc/self/map_files/%lx-10000000000000000%lx", a, b);
} else
return 1;
return 0;
}
@@ -0,0 +1,58 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
static inline ssize_t sys_read(int fd, void *buf, size_t len)
{
return syscall(SYS_read, fd, buf, len);
}
int main(void)
{
char buf1[64];
char buf2[64];
int fd;
ssize_t rv;
fd = open("/proc/self/syscall", O_RDONLY);
if (fd == -1) {
if (errno == ENOENT)
return 4;
return 1;
}
/* Do direct system call as libc can wrap anything. */
snprintf(buf1, sizeof(buf1), "%ld 0x%lx 0x%lx 0x%lx",
(long)SYS_read, (long)fd, (long)buf2, (long)sizeof(buf2));
memset(buf2, 0, sizeof(buf2));
rv = sys_read(fd, buf2, sizeof(buf2));
if (rv < 0)
return 1;
if (rv < strlen(buf1))
return 1;
if (strncmp(buf1, buf2, strlen(buf1)) != 0)
return 1;
return 0;
}
@@ -0,0 +1,40 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
int main(void)
{
char buf[64];
int fd;
fd = open("/proc/self/wchan", O_RDONLY);
if (fd == -1) {
if (errno == ENOENT)
return 4;
return 1;
}
buf[0] = '\0';
if (read(fd, buf, sizeof(buf)) != 1)
return 1;
if (buf[0] != '0')
return 1;
return 0;
}
@@ -0,0 +1,121 @@
/*
* Copyright (c) 2021 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Test that "mount -t proc -o subset=pid" hides everything but pids,
* /proc/self and /proc/thread-self.
*/
#undef NDEBUG
#include <assert.h>
#include <errno.h>
#include <sched.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <stdio.h>
static inline bool streq(const char *a, const char *b)
{
return strcmp(a, b) == 0;
}
static void make_private_proc(void)
{
if (unshare(CLONE_NEWNS) == -1) {
if (errno == ENOSYS || errno == EPERM) {
exit(4);
}
exit(1);
}
if (mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL) == -1) {
exit(1);
}
if (mount(NULL, "/proc", "proc", 0, "subset=pid") == -1) {
exit(1);
}
}
static bool string_is_pid(const char *s)
{
while (1) {
switch (*s++) {
case '0':case '1':case '2':case '3':case '4':
case '5':case '6':case '7':case '8':case '9':
continue;
case '\0':
return true;
default:
return false;
}
}
}
int main(void)
{
make_private_proc();
DIR *d = opendir("/proc");
assert(d);
struct dirent *de;
bool dot = false;
bool dot_dot = false;
bool self = false;
bool thread_self = false;
while ((de = readdir(d))) {
if (streq(de->d_name, ".")) {
assert(!dot);
dot = true;
assert(de->d_type == DT_DIR);
} else if (streq(de->d_name, "..")) {
assert(!dot_dot);
dot_dot = true;
assert(de->d_type == DT_DIR);
} else if (streq(de->d_name, "self")) {
assert(!self);
self = true;
assert(de->d_type == DT_LNK);
} else if (streq(de->d_name, "thread-self")) {
assert(!thread_self);
thread_self = true;
assert(de->d_type == DT_LNK);
} else {
if (!string_is_pid(de->d_name)) {
fprintf(stderr, "d_name '%s'\n", de->d_name);
assert(0);
}
assert(de->d_type == DT_DIR);
}
}
char c;
int rv = readlink("/proc/cpuinfo", &c, 1);
assert(rv == -1 && errno == ENOENT);
int fd = open("/proc/cpuinfo", O_RDONLY);
assert(fd == -1 && errno == ENOENT);
return 0;
}
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2021 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Test that /proc/*/task never contains "0".
#include <sys/types.h>
#include <dirent.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
static pid_t pid = -1;
static void atexit_hook(void)
{
if (pid > 0) {
kill(pid, SIGKILL);
}
}
static void *f(void *_)
{
return NULL;
}
static void sigalrm(int _)
{
exit(0);
}
int main(void)
{
pid = fork();
if (pid == 0) {
/* child */
while (1) {
pthread_t pth;
pthread_create(&pth, NULL, f, NULL);
pthread_join(pth, NULL);
}
} else if (pid > 0) {
/* parent */
atexit(atexit_hook);
char buf[64];
snprintf(buf, sizeof(buf), "/proc/%u/task", pid);
signal(SIGALRM, sigalrm);
alarm(1);
while (1) {
DIR *d = opendir(buf);
struct dirent *de;
while ((de = readdir(d))) {
if (strcmp(de->d_name, "0") == 0) {
exit(1);
}
}
closedir(d);
}
return 0;
} else {
perror("fork");
return 1;
}
}
@@ -0,0 +1,58 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Test that boottime value in /proc/uptime and CLOCK_BOOTTIME increment
// monotonically. We don't test idle time monotonicity due to broken iowait
// task counting, cf: comment above get_cpu_idle_time_us()
#undef NDEBUG
#include <assert.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "proc-uptime.h"
int main(void)
{
uint64_t start, u0, u1, c0, c1;
int fd;
fd = open("/proc/uptime", O_RDONLY);
assert(fd >= 0);
u0 = proc_uptime(fd);
start = u0;
c0 = clock_boottime();
do {
u1 = proc_uptime(fd);
c1 = clock_boottime();
/* Is /proc/uptime monotonic ? */
assert(u1 >= u0);
/* Is CLOCK_BOOTTIME monotonic ? */
assert(c1 >= c0);
/* Is CLOCK_BOOTTIME VS /proc/uptime monotonic ? */
assert(c0 >= u0);
u0 = u1;
c0 = c1;
} while (u1 - start < 100);
return 0;
}
@@ -0,0 +1,92 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Test that boottime value in /proc/uptime and CLOCK_BOOTTIME increment
// monotonically while shifting across CPUs. We don't test idle time
// monotonicity due to broken iowait task counting, cf: comment above
// get_cpu_idle_time_us()
#undef NDEBUG
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "proc-uptime.h"
static inline int sys_sched_getaffinity(pid_t pid, unsigned int len, unsigned long *m)
{
return syscall(SYS_sched_getaffinity, pid, len, m);
}
static inline int sys_sched_setaffinity(pid_t pid, unsigned int len, unsigned long *m)
{
return syscall(SYS_sched_setaffinity, pid, len, m);
}
int main(void)
{
uint64_t u0, u1, c0, c1;
unsigned int len;
unsigned long *m;
unsigned int cpu;
int fd;
/* find out "nr_cpu_ids" */
m = NULL;
len = 0;
do {
len += sizeof(unsigned long);
free(m);
m = malloc(len);
} while (sys_sched_getaffinity(0, len, m) == -1 && errno == EINVAL);
fd = open("/proc/uptime", O_RDONLY);
assert(fd >= 0);
u0 = proc_uptime(fd);
c0 = clock_boottime();
for (cpu = 0; cpu < len * 8; cpu++) {
memset(m, 0, len);
m[cpu / (8 * sizeof(unsigned long))] |= 1UL << (cpu % (8 * sizeof(unsigned long)));
/* CPU might not exist, ignore error */
sys_sched_setaffinity(0, len, m);
u1 = proc_uptime(fd);
c1 = clock_boottime();
/* Is /proc/uptime monotonic ? */
assert(u1 >= u0);
/* Is CLOCK_BOOTTIME monotonic ? */
assert(c1 >= c0);
/* Is CLOCK_BOOTTIME VS /proc/uptime monotonic ? */
assert(c0 >= u0);
u0 = u1;
c0 = c1;
}
return 0;
}
@@ -0,0 +1,60 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#undef NDEBUG
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include "proc.h"
static uint64_t clock_boottime(void)
{
struct timespec ts;
int err;
err = clock_gettime(CLOCK_BOOTTIME, &ts);
assert(err >= 0);
return (ts.tv_sec * 100) + (ts.tv_nsec / 10000000);
}
static uint64_t proc_uptime(int fd)
{
uint64_t val1, val2;
char buf[64], *p;
ssize_t rv;
/* save "p < end" checks */
memset(buf, 0, sizeof(buf));
rv = pread(fd, buf, sizeof(buf), 0);
assert(0 <= rv && rv <= sizeof(buf));
buf[sizeof(buf) - 1] = '\0';
p = buf;
val1 = xstrtoull(p, &p);
assert(p[0] == '.');
assert('0' <= p[1] && p[1] <= '9');
assert('0' <= p[2] && p[2] <= '9');
assert(p[3] == ' ');
val2 = (p[1] - '0') * 10 + p[2] - '0';
return val1 * 100 + val2;
}
@@ -0,0 +1,51 @@
#pragma once
#undef NDEBUG
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
static inline pid_t sys_getpid(void)
{
return syscall(SYS_getpid);
}
static inline pid_t sys_gettid(void)
{
return syscall(SYS_gettid);
}
static inline bool streq(const char *s1, const char *s2)
{
return strcmp(s1, s2) == 0;
}
static unsigned long long xstrtoull(const char *p, char **end)
{
if (*p == '0') {
*end = (char *)p + 1;
return 0;
} else if ('1' <= *p && *p <= '9') {
unsigned long long val;
errno = 0;
val = strtoull(p, end, 10);
assert(errno == 0);
return val;
} else
assert(0);
}
static struct dirent *xreaddir(DIR *d)
{
struct dirent *de;
errno = 0;
de = readdir(d);
assert(de || errno == 0);
return de;
}
@@ -0,0 +1,148 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Test
// 1) read and lseek on every file in /proc
// 2) readlink of every symlink in /proc
// 3) recursively (1) + (2) for every directory in /proc
// 4) write to /proc/*/clear_refs and /proc/*/task/*/clear_refs
// 5) write to /proc/sysrq-trigger
#undef NDEBUG
#include <assert.h>
#include <errno.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <fcntl.h>
#include <unistd.h>
#include "proc.h"
static void f_reg(DIR *d, const char *filename)
{
char buf[4096];
int fd;
ssize_t rv;
/* read from /proc/kmsg can block */
fd = openat(dirfd(d), filename, O_RDONLY|O_NONBLOCK);
if (fd == -1)
return;
/* struct proc_ops::proc_lseek is mandatory if file is seekable. */
(void)lseek(fd, 0, SEEK_SET);
rv = read(fd, buf, sizeof(buf));
assert((0 <= rv && rv <= sizeof(buf)) || rv == -1);
close(fd);
}
static void f_reg_write(DIR *d, const char *filename, const char *buf, size_t len)
{
int fd;
ssize_t rv;
fd = openat(dirfd(d), filename, O_WRONLY);
if (fd == -1)
return;
rv = write(fd, buf, len);
assert((0 <= rv && rv <= len) || rv == -1);
close(fd);
}
static void f_lnk(DIR *d, const char *filename)
{
char buf[4096];
ssize_t rv;
rv = readlinkat(dirfd(d), filename, buf, sizeof(buf));
assert((0 <= rv && rv <= sizeof(buf)) || rv == -1);
}
static void f(DIR *d, unsigned int level)
{
struct dirent *de;
de = xreaddir(d);
assert(de->d_type == DT_DIR);
assert(streq(de->d_name, "."));
de = xreaddir(d);
assert(de->d_type == DT_DIR);
assert(streq(de->d_name, ".."));
while ((de = xreaddir(d))) {
assert(!streq(de->d_name, "."));
assert(!streq(de->d_name, ".."));
switch (de->d_type) {
DIR *dd;
int fd;
case DT_REG:
if (level == 0 && streq(de->d_name, "sysrq-trigger")) {
f_reg_write(d, de->d_name, "h", 1);
} else if (level == 1 && streq(de->d_name, "clear_refs")) {
f_reg_write(d, de->d_name, "1", 1);
} else if (level == 3 && streq(de->d_name, "clear_refs")) {
f_reg_write(d, de->d_name, "1", 1);
} else {
f_reg(d, de->d_name);
}
break;
case DT_DIR:
fd = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY);
if (fd == -1)
continue;
dd = fdopendir(fd);
if (!dd)
continue;
f(dd, level + 1);
closedir(dd);
break;
case DT_LNK:
f_lnk(d, de->d_name);
break;
default:
assert(0);
}
}
}
int main(void)
{
DIR *d;
struct statfs sfs;
d = opendir("/proc");
if (!d)
return 4;
/* Ensure /proc is proc. */
if (fstatfs(dirfd(d), &sfs) == -1) {
return 1;
}
if (sfs.f_type != 0x9fa0) {
fprintf(stderr, "error: unexpected f_type %lx\n", (long)sfs.f_type);
return 2;
}
f(d, 0);
return 0;
}
@@ -0,0 +1,39 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Test that /proc/self gives correct TGID.
#undef NDEBUG
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include "proc.h"
int main(void)
{
char buf1[64], buf2[64];
pid_t pid;
ssize_t rv;
pid = sys_getpid();
snprintf(buf1, sizeof(buf1), "%u", pid);
rv = readlink("/proc/self", buf2, sizeof(buf2));
assert(rv == strlen(buf1));
buf2[rv] = '\0';
assert(streq(buf1, buf2));
return 0;
}
@@ -0,0 +1,129 @@
/*
* Copyright © 2019 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Test that setns(CLONE_NEWNET) points to new /proc/net content even
* if old one is in dcache.
*
* FIXME /proc/net/unix is under CONFIG_UNIX which can be disabled.
*/
#undef NDEBUG
#include <assert.h>
#include <errno.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
static pid_t pid = -1;
static void f(void)
{
if (pid > 0) {
kill(pid, SIGTERM);
}
}
int main(void)
{
int fd[2];
char _ = 0;
int nsfd;
atexit(f);
/* Check for priviledges and syscall availability straight away. */
if (unshare(CLONE_NEWNET) == -1) {
if (errno == ENOSYS || errno == EPERM) {
return 4;
}
return 1;
}
/* Distinguisher between two otherwise empty net namespaces. */
if (socket(AF_UNIX, SOCK_STREAM, 0) == -1) {
return 1;
}
if (pipe(fd) == -1) {
return 1;
}
pid = fork();
if (pid == -1) {
return 1;
}
if (pid == 0) {
if (unshare(CLONE_NEWNET) == -1) {
return 1;
}
if (write(fd[1], &_, 1) != 1) {
return 1;
}
pause();
return 0;
}
if (read(fd[0], &_, 1) != 1) {
return 1;
}
{
char buf[64];
snprintf(buf, sizeof(buf), "/proc/%u/ns/net", pid);
nsfd = open(buf, O_RDONLY);
if (nsfd == -1) {
return 1;
}
}
/* Reliably pin dentry into dcache. */
(void)open("/proc/net/unix", O_RDONLY);
if (setns(nsfd, CLONE_NEWNET) == -1) {
return 1;
}
kill(pid, SIGTERM);
pid = 0;
{
char buf[4096];
ssize_t rv;
int fd;
fd = open("/proc/net/unix", O_RDONLY);
if (fd == -1) {
return 1;
}
#define S "Num RefCount Protocol Flags Type St Inode Path\n"
rv = read(fd, buf, sizeof(buf));
assert(rv == strlen(S));
assert(memcmp(buf, S, strlen(S)) == 0);
}
return 0;
}
@@ -0,0 +1,133 @@
/*
* Copyright © 2019 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Test that setns(CLONE_NEWIPC) points to new /proc/sysvipc content even
* if old one is in dcache.
*/
#undef NDEBUG
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
static pid_t pid = -1;
static void f(void)
{
if (pid > 0) {
kill(pid, SIGTERM);
}
}
int main(void)
{
int fd[2];
char _ = 0;
int nsfd;
atexit(f);
/* Check for priviledges and syscall availability straight away. */
if (unshare(CLONE_NEWIPC) == -1) {
if (errno == ENOSYS || errno == EPERM) {
return 4;
}
return 1;
}
/* Distinguisher between two otherwise empty IPC namespaces. */
if (shmget(IPC_PRIVATE, 1, IPC_CREAT) == -1) {
return 1;
}
if (pipe(fd) == -1) {
return 1;
}
pid = fork();
if (pid == -1) {
return 1;
}
if (pid == 0) {
if (unshare(CLONE_NEWIPC) == -1) {
return 1;
}
if (write(fd[1], &_, 1) != 1) {
return 1;
}
pause();
return 0;
}
if (read(fd[0], &_, 1) != 1) {
return 1;
}
{
char buf[64];
snprintf(buf, sizeof(buf), "/proc/%u/ns/ipc", pid);
nsfd = open(buf, O_RDONLY);
if (nsfd == -1) {
return 1;
}
}
/* Reliably pin dentry into dcache. */
(void)open("/proc/sysvipc/shm", O_RDONLY);
if (setns(nsfd, CLONE_NEWIPC) == -1) {
return 1;
}
kill(pid, SIGTERM);
pid = 0;
{
char buf[4096];
ssize_t rv;
int fd;
fd = open("/proc/sysvipc/shm", O_RDONLY);
if (fd == -1) {
return 1;
}
#define S32 " key shmid perms size cpid lpid nattch uid gid cuid cgid atime dtime ctime rss swap\n"
#define S64 " key shmid perms size cpid lpid nattch uid gid cuid cgid atime dtime ctime rss swap\n"
rv = read(fd, buf, sizeof(buf));
if (rv == strlen(S32)) {
assert(memcmp(buf, S32, strlen(S32)) == 0);
} else if (rv == strlen(S64)) {
assert(memcmp(buf, S64, strlen(S64)) == 0);
} else {
assert(0);
}
}
return 0;
}
@@ -0,0 +1,64 @@
/*
* Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Test that /proc/thread-self gives correct TGID/PID.
#undef NDEBUG
#include <assert.h>
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include "proc.h"
int f(void *arg)
{
char buf1[64], buf2[64];
pid_t pid, tid;
ssize_t rv;
pid = sys_getpid();
tid = sys_gettid();
snprintf(buf1, sizeof(buf1), "%u/task/%u", pid, tid);
rv = readlink("/proc/thread-self", buf2, sizeof(buf2));
assert(rv == strlen(buf1));
buf2[rv] = '\0';
assert(streq(buf1, buf2));
if (arg)
exit(0);
return 0;
}
int main(void)
{
const int PAGE_SIZE = sysconf(_SC_PAGESIZE);
pid_t pid;
void *stack;
/* main thread */
f((void *)0);
stack = mmap(NULL, 2 * PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
assert(stack != MAP_FAILED);
/* side thread */
pid = clone(f, stack + PAGE_SIZE, CLONE_THREAD|CLONE_SIGHAND|CLONE_VM, (void *)1);
assert(pid > 0);
pause();
return 0;
}