Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tools/testing/selftests/proc: test /proc/*/fd a bit (+ PF_KTHREAD is ABI!)

* Test lookup in /proc/self/fd.
"map_files" lookup story showed that lookup is not that simple.

* Test that all those symlinks open the same file.
Check with (st_dev, st_info).

* Test that kernel threads do not have anything in their /proc/*/fd/
directory.

Now this is where things get interesting.

First, kernel threads aren't pinned by /proc/self or equivalent,
thus some "atomicity" is required.

Second, ->comm can contain whitespace and ')'.
No, they are not escaped.

Third, the only reliable way to check if process is kernel thread
appears to be field #9 in /proc/*/stat.

This field is struct task_struct::flags in decimal!
Check is done by testing PF_KTHREAD flags like we do in kernel.

PF_KTREAD value is a part of userspace ABI !!!

Other methods for determining kernel threadness are not reliable:
* RSS can be 0 if everything is swapped, even while reading
from /proc/self.

* ->total_vm CAN BE ZERO if process is finishing

munmap(NULL, whole address space);

* /proc/*/maps and similar files can be empty because unmapping
everything works. Read returning 0 can't distinguish between
kernel thread and such suicide process.

Link: http://lkml.kernel.org/r/20180505000414.GA15090@avx2
Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Alexey Dobriyan and committed by
Linus Torvalds
b2f5de03 5d008fb4

+451 -32
+3
tools/testing/selftests/proc/.gitignore
··· 1 + /fd-001-lookup 2 + /fd-002-posix-eq 3 + /fd-003-kthread 1 4 /proc-loadavg-001 2 5 /proc-self-map-files-001 3 6 /proc-self-map-files-002
+4 -1
tools/testing/selftests/proc/Makefile
··· 1 - CFLAGS += -Wall -O2 1 + CFLAGS += -Wall -O2 -Wno-unused-function 2 2 3 3 TEST_GEN_PROGS := 4 + TEST_GEN_PROGS += fd-001-lookup 5 + TEST_GEN_PROGS += fd-002-posix-eq 6 + TEST_GEN_PROGS += fd-003-kthread 4 7 TEST_GEN_PROGS += proc-loadavg-001 5 8 TEST_GEN_PROGS += proc-self-map-files-001 6 9 TEST_GEN_PROGS += proc-self-map-files-002
+168
tools/testing/selftests/proc/fd-001-lookup.c
··· 1 + /* 2 + * Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com> 3 + * 4 + * Permission to use, copy, modify, and distribute this software for any 5 + * purpose with or without fee is hereby granted, provided that the above 6 + * copyright notice and this permission notice appear in all copies. 7 + * 8 + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 + */ 16 + // Test /proc/*/fd lookup. 17 + #define _GNU_SOURCE 18 + #undef NDEBUG 19 + #include <assert.h> 20 + #include <dirent.h> 21 + #include <errno.h> 22 + #include <limits.h> 23 + #include <sched.h> 24 + #include <stdio.h> 25 + #include <unistd.h> 26 + #include <sys/types.h> 27 + #include <sys/stat.h> 28 + #include <fcntl.h> 29 + 30 + #include "proc.h" 31 + 32 + /* lstat(2) has more "coverage" in case non-symlink pops up somehow. */ 33 + static void test_lookup_pass(const char *pathname) 34 + { 35 + struct stat st; 36 + ssize_t rv; 37 + 38 + memset(&st, 0, sizeof(struct stat)); 39 + rv = lstat(pathname, &st); 40 + assert(rv == 0); 41 + assert(S_ISLNK(st.st_mode)); 42 + } 43 + 44 + static void test_lookup_fail(const char *pathname) 45 + { 46 + struct stat st; 47 + ssize_t rv; 48 + 49 + rv = lstat(pathname, &st); 50 + assert(rv == -1 && errno == ENOENT); 51 + } 52 + 53 + static void test_lookup(unsigned int fd) 54 + { 55 + char buf[64]; 56 + unsigned int c; 57 + unsigned int u; 58 + int i; 59 + 60 + snprintf(buf, sizeof(buf), "/proc/self/fd/%u", fd); 61 + test_lookup_pass(buf); 62 + 63 + /* leading junk */ 64 + for (c = 1; c <= 255; c++) { 65 + if (c == '/') 66 + continue; 67 + snprintf(buf, sizeof(buf), "/proc/self/fd/%c%u", c, fd); 68 + test_lookup_fail(buf); 69 + } 70 + 71 + /* trailing junk */ 72 + for (c = 1; c <= 255; c++) { 73 + if (c == '/') 74 + continue; 75 + snprintf(buf, sizeof(buf), "/proc/self/fd/%u%c", fd, c); 76 + test_lookup_fail(buf); 77 + } 78 + 79 + for (i = INT_MIN; i < INT_MIN + 1024; i++) { 80 + snprintf(buf, sizeof(buf), "/proc/self/fd/%d", i); 81 + test_lookup_fail(buf); 82 + } 83 + for (i = -1024; i < 0; i++) { 84 + snprintf(buf, sizeof(buf), "/proc/self/fd/%d", i); 85 + test_lookup_fail(buf); 86 + } 87 + for (u = INT_MAX - 1024; u <= (unsigned int)INT_MAX + 1024; u++) { 88 + snprintf(buf, sizeof(buf), "/proc/self/fd/%u", u); 89 + test_lookup_fail(buf); 90 + } 91 + for (u = UINT_MAX - 1024; u != 0; u++) { 92 + snprintf(buf, sizeof(buf), "/proc/self/fd/%u", u); 93 + test_lookup_fail(buf); 94 + } 95 + 96 + 97 + } 98 + 99 + int main(void) 100 + { 101 + struct dirent *de; 102 + unsigned int fd, target_fd; 103 + 104 + if (unshare(CLONE_FILES) == -1) 105 + return 1; 106 + 107 + /* Wipe fdtable. */ 108 + do { 109 + DIR *d; 110 + 111 + d = opendir("/proc/self/fd"); 112 + if (!d) 113 + return 1; 114 + 115 + de = xreaddir(d); 116 + assert(de->d_type == DT_DIR); 117 + assert(streq(de->d_name, ".")); 118 + 119 + de = xreaddir(d); 120 + assert(de->d_type == DT_DIR); 121 + assert(streq(de->d_name, "..")); 122 + next: 123 + de = xreaddir(d); 124 + if (de) { 125 + unsigned long long fd_ull; 126 + unsigned int fd; 127 + char *end; 128 + 129 + assert(de->d_type == DT_LNK); 130 + 131 + fd_ull = xstrtoull(de->d_name, &end); 132 + assert(*end == '\0'); 133 + assert(fd_ull == (unsigned int)fd_ull); 134 + 135 + fd = fd_ull; 136 + if (fd == dirfd(d)) 137 + goto next; 138 + close(fd); 139 + } 140 + 141 + closedir(d); 142 + } while (de); 143 + 144 + /* Now fdtable is clean. */ 145 + 146 + fd = open("/", O_PATH|O_DIRECTORY); 147 + assert(fd == 0); 148 + test_lookup(fd); 149 + close(fd); 150 + 151 + /* Clean again! */ 152 + 153 + fd = open("/", O_PATH|O_DIRECTORY); 154 + assert(fd == 0); 155 + /* Default RLIMIT_NOFILE-1 */ 156 + target_fd = 1023; 157 + while (target_fd > 0) { 158 + if (dup2(fd, target_fd) == target_fd) 159 + break; 160 + target_fd /= 2; 161 + } 162 + assert(target_fd > 0); 163 + close(fd); 164 + test_lookup(target_fd); 165 + close(target_fd); 166 + 167 + return 0; 168 + }
+57
tools/testing/selftests/proc/fd-002-posix-eq.c
··· 1 + /* 2 + * Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com> 3 + * 4 + * Permission to use, copy, modify, and distribute this software for any 5 + * purpose with or without fee is hereby granted, provided that the above 6 + * copyright notice and this permission notice appear in all copies. 7 + * 8 + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 + */ 16 + // Test that open(/proc/*/fd/*) opens the same file. 17 + #undef NDEBUG 18 + #include <assert.h> 19 + #include <stdio.h> 20 + #include <sys/types.h> 21 + #include <sys/stat.h> 22 + #include <fcntl.h> 23 + #include <unistd.h> 24 + 25 + int main(void) 26 + { 27 + int fd0, fd1, fd2; 28 + struct stat st0, st1, st2; 29 + char buf[64]; 30 + int rv; 31 + 32 + fd0 = open("/", O_DIRECTORY|O_RDONLY); 33 + assert(fd0 >= 0); 34 + 35 + snprintf(buf, sizeof(buf), "/proc/self/fd/%u", fd0); 36 + fd1 = open(buf, O_RDONLY); 37 + assert(fd1 >= 0); 38 + 39 + snprintf(buf, sizeof(buf), "/proc/thread-self/fd/%u", fd0); 40 + fd2 = open(buf, O_RDONLY); 41 + assert(fd2 >= 0); 42 + 43 + rv = fstat(fd0, &st0); 44 + assert(rv == 0); 45 + rv = fstat(fd1, &st1); 46 + assert(rv == 0); 47 + rv = fstat(fd2, &st2); 48 + assert(rv == 0); 49 + 50 + assert(st0.st_dev == st1.st_dev); 51 + assert(st0.st_ino == st1.st_ino); 52 + 53 + assert(st0.st_dev == st2.st_dev); 54 + assert(st0.st_ino == st2.st_ino); 55 + 56 + return 0; 57 + }
+178
tools/testing/selftests/proc/fd-003-kthread.c
··· 1 + /* 2 + * Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com> 3 + * 4 + * Permission to use, copy, modify, and distribute this software for any 5 + * purpose with or without fee is hereby granted, provided that the above 6 + * copyright notice and this permission notice appear in all copies. 7 + * 8 + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 + */ 16 + // Test that /proc/$KERNEL_THREAD/fd/ is empty. 17 + #define _GNU_SOURCE 18 + #undef NDEBUG 19 + #include <sys/syscall.h> 20 + #include <assert.h> 21 + #include <dirent.h> 22 + #include <limits.h> 23 + #include <stdio.h> 24 + #include <string.h> 25 + #include <sys/types.h> 26 + #include <sys/stat.h> 27 + #include <fcntl.h> 28 + #include <unistd.h> 29 + 30 + #include "proc.h" 31 + 32 + #define PF_KHTREAD 0x00200000 33 + 34 + /* 35 + * Test for kernel threadness atomically with openat(). 36 + * 37 + * Return /proc/$PID/fd descriptor if process is kernel thread. 38 + * Return -1 if a process is userspace process. 39 + */ 40 + static int kernel_thread_fd(unsigned int pid) 41 + { 42 + unsigned int flags = 0; 43 + char buf[4096]; 44 + int dir_fd, fd; 45 + ssize_t rv; 46 + 47 + snprintf(buf, sizeof(buf), "/proc/%u", pid); 48 + dir_fd = open(buf, O_RDONLY|O_DIRECTORY); 49 + if (dir_fd == -1) 50 + return -1; 51 + 52 + /* 53 + * Believe it or not, struct task_struct::flags is directly exposed 54 + * to userspace! 55 + */ 56 + fd = openat(dir_fd, "stat", O_RDONLY); 57 + if (fd == -1) { 58 + close(dir_fd); 59 + return -1; 60 + } 61 + rv = read(fd, buf, sizeof(buf)); 62 + close(fd); 63 + if (0 < rv && rv <= sizeof(buf)) { 64 + unsigned long long flags_ull; 65 + char *p, *end; 66 + int i; 67 + 68 + assert(buf[rv - 1] == '\n'); 69 + buf[rv - 1] = '\0'; 70 + 71 + /* Search backwards: ->comm can contain whitespace and ')'. */ 72 + for (i = 0; i < 43; i++) { 73 + p = strrchr(buf, ' '); 74 + assert(p); 75 + *p = '\0'; 76 + } 77 + 78 + p = strrchr(buf, ' '); 79 + assert(p); 80 + 81 + flags_ull = xstrtoull(p + 1, &end); 82 + assert(*end == '\0'); 83 + assert(flags_ull == (unsigned int)flags_ull); 84 + 85 + flags = flags_ull; 86 + } 87 + 88 + fd = -1; 89 + if (flags & PF_KHTREAD) { 90 + fd = openat(dir_fd, "fd", O_RDONLY|O_DIRECTORY); 91 + } 92 + close(dir_fd); 93 + return fd; 94 + } 95 + 96 + static void test_readdir(int fd) 97 + { 98 + DIR *d; 99 + struct dirent *de; 100 + 101 + d = fdopendir(fd); 102 + assert(d); 103 + 104 + de = xreaddir(d); 105 + assert(streq(de->d_name, ".")); 106 + assert(de->d_type == DT_DIR); 107 + 108 + de = xreaddir(d); 109 + assert(streq(de->d_name, "..")); 110 + assert(de->d_type == DT_DIR); 111 + 112 + de = xreaddir(d); 113 + assert(!de); 114 + } 115 + 116 + static inline int sys_statx(int dirfd, const char *pathname, int flags, 117 + unsigned int mask, void *stx) 118 + { 119 + return syscall(SYS_statx, dirfd, pathname, flags, mask, stx); 120 + } 121 + 122 + static void test_lookup_fail(int fd, const char *pathname) 123 + { 124 + char stx[256] __attribute__((aligned(8))); 125 + int rv; 126 + 127 + rv = sys_statx(fd, pathname, AT_SYMLINK_NOFOLLOW, 0, (void *)stx); 128 + assert(rv == -1 && errno == ENOENT); 129 + } 130 + 131 + static void test_lookup(int fd) 132 + { 133 + char buf[64]; 134 + unsigned int u; 135 + int i; 136 + 137 + for (i = INT_MIN; i < INT_MIN + 1024; i++) { 138 + snprintf(buf, sizeof(buf), "%d", i); 139 + test_lookup_fail(fd, buf); 140 + } 141 + for (i = -1024; i < 1024; i++) { 142 + snprintf(buf, sizeof(buf), "%d", i); 143 + test_lookup_fail(fd, buf); 144 + } 145 + for (u = INT_MAX - 1024; u < (unsigned int)INT_MAX + 1024; u++) { 146 + snprintf(buf, sizeof(buf), "%u", u); 147 + test_lookup_fail(fd, buf); 148 + } 149 + for (u = UINT_MAX - 1024; u != 0; u++) { 150 + snprintf(buf, sizeof(buf), "%u", u); 151 + test_lookup_fail(fd, buf); 152 + } 153 + } 154 + 155 + int main(void) 156 + { 157 + unsigned int pid; 158 + int fd; 159 + 160 + /* 161 + * In theory this will loop indefinitely if kernel threads are exiled 162 + * from /proc. 163 + * 164 + * Start with kthreadd. 165 + */ 166 + pid = 2; 167 + while ((fd = kernel_thread_fd(pid)) == -1 && pid < 1024) { 168 + pid++; 169 + } 170 + /* EACCES if run as non-root. */ 171 + if (pid >= 1024) 172 + return 1; 173 + 174 + test_readdir(fd); 175 + test_lookup(fd); 176 + 177 + return 0; 178 + }
+1 -15
tools/testing/selftests/proc/proc-uptime.h
··· 20 20 #include <stdlib.h> 21 21 #include <unistd.h> 22 22 23 - static unsigned long long xstrtoull(const char *p, char **end) 24 - { 25 - if (*p == '0') { 26 - *end = (char *)p + 1; 27 - return 0; 28 - } else if ('1' <= *p && *p <= '9') { 29 - unsigned long long val; 30 - 31 - errno = 0; 32 - val = strtoull(p, end, 10); 33 - assert(errno == 0); 34 - return val; 35 - } else 36 - assert(0); 37 - } 23 + #include "proc.h" 38 24 39 25 static void proc_uptime(int fd, uint64_t *uptime, uint64_t *idle) 40 26 {
+39
tools/testing/selftests/proc/proc.h
··· 1 + #pragma once 2 + #undef NDEBUG 3 + #include <assert.h> 4 + #include <dirent.h> 5 + #include <errno.h> 6 + #include <stdbool.h> 7 + #include <stdlib.h> 8 + #include <string.h> 9 + 10 + static inline bool streq(const char *s1, const char *s2) 11 + { 12 + return strcmp(s1, s2) == 0; 13 + } 14 + 15 + static unsigned long long xstrtoull(const char *p, char **end) 16 + { 17 + if (*p == '0') { 18 + *end = (char *)p + 1; 19 + return 0; 20 + } else if ('1' <= *p && *p <= '9') { 21 + unsigned long long val; 22 + 23 + errno = 0; 24 + val = strtoull(p, end, 10); 25 + assert(errno == 0); 26 + return val; 27 + } else 28 + assert(0); 29 + } 30 + 31 + static struct dirent *xreaddir(DIR *d) 32 + { 33 + struct dirent *de; 34 + 35 + errno = 0; 36 + de = readdir(d); 37 + assert(de || errno == 0); 38 + return de; 39 + }
+1 -16
tools/testing/selftests/proc/read.c
··· 31 31 #include <fcntl.h> 32 32 #include <unistd.h> 33 33 34 - static inline bool streq(const char *s1, const char *s2) 35 - { 36 - return strcmp(s1, s2) == 0; 37 - } 38 - 39 - static struct dirent *xreaddir(DIR *d) 40 - { 41 - struct dirent *de; 42 - 43 - errno = 0; 44 - de = readdir(d); 45 - if (!de && errno != 0) { 46 - exit(1); 47 - } 48 - return de; 49 - } 34 + #include "proc.h" 50 35 51 36 static void f_reg(DIR *d, const char *filename) 52 37 {