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

secretmem: test: add basic selftest for memfd_secret(2)

The test verifies that file descriptor created with memfd_secret does not
allow read/write operations, that secret memory mappings respect
RLIMIT_MEMLOCK and that remote accesses with process_vm_read() and
ptrace() to the secret memory fail.

Link: https://lkml.kernel.org/r/20210518072034.31572-8-rppt@kernel.org
Signed-off-by: Mike Rapoport <rppt@linux.ibm.com>
Acked-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Christopher Lameter <cl@linux.com>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: David Hildenbrand <david@redhat.com>
Cc: Elena Reshetova <elena.reshetova@intel.com>
Cc: Hagen Paul Pfeifer <hagen@jauu.net>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Bottomley <jejb@linux.ibm.com>
Cc: "Kirill A. Shutemov" <kirill@shutemov.name>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Michael Kerrisk <mtk.manpages@gmail.com>
Cc: Palmer Dabbelt <palmer@dabbelt.com>
Cc: Palmer Dabbelt <palmerdabbelt@google.com>
Cc: Paul Walmsley <paul.walmsley@sifive.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Rick Edgecombe <rick.p.edgecombe@intel.com>
Cc: Roman Gushchin <guro@fb.com>
Cc: Shakeel Butt <shakeelb@google.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Tycho Andersen <tycho@tycho.ws>
Cc: Will Deacon <will@kernel.org>
Cc: kernel test robot <lkp@intel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Mike Rapoport and committed by
Linus Torvalds
76fe17ef 7bb7f2ac

+316 -1
+1
tools/testing/selftests/vm/.gitignore
··· 24 24 map_fixed_noreplace 25 25 write_to_hugetlbfs 26 26 hmm-tests 27 + memfd_secret 27 28 local_config.* 28 29 split_huge_page_test
+2 -1
tools/testing/selftests/vm/Makefile
··· 35 35 TEST_GEN_FILES += map_fixed_noreplace 36 36 TEST_GEN_FILES += map_hugetlb 37 37 TEST_GEN_FILES += map_populate 38 + TEST_GEN_FILES += memfd_secret 38 39 TEST_GEN_FILES += mlock-random-test 39 40 TEST_GEN_FILES += mlock2-tests 40 41 TEST_GEN_FILES += mremap_dontunmap ··· 136 135 endif 137 136 endif 138 137 139 - $(OUTPUT)/mlock-random-test: LDLIBS += -lcap 138 + $(OUTPUT)/mlock-random-test $(OUTPUT)/memfd_secret: LDLIBS += -lcap 140 139 141 140 $(OUTPUT)/gup_test: ../../../../mm/gup_test.h 142 141
+296
tools/testing/selftests/vm/memfd_secret.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright IBM Corporation, 2021 4 + * 5 + * Author: Mike Rapoport <rppt@linux.ibm.com> 6 + */ 7 + 8 + #define _GNU_SOURCE 9 + #include <sys/uio.h> 10 + #include <sys/mman.h> 11 + #include <sys/wait.h> 12 + #include <sys/types.h> 13 + #include <sys/ptrace.h> 14 + #include <sys/syscall.h> 15 + #include <sys/resource.h> 16 + #include <sys/capability.h> 17 + 18 + #include <stdlib.h> 19 + #include <string.h> 20 + #include <unistd.h> 21 + #include <errno.h> 22 + #include <stdio.h> 23 + 24 + #include "../kselftest.h" 25 + 26 + #define fail(fmt, ...) ksft_test_result_fail(fmt, ##__VA_ARGS__) 27 + #define pass(fmt, ...) ksft_test_result_pass(fmt, ##__VA_ARGS__) 28 + #define skip(fmt, ...) ksft_test_result_skip(fmt, ##__VA_ARGS__) 29 + 30 + #ifdef __NR_memfd_secret 31 + 32 + #define PATTERN 0x55 33 + 34 + static const int prot = PROT_READ | PROT_WRITE; 35 + static const int mode = MAP_SHARED; 36 + 37 + static unsigned long page_size; 38 + static unsigned long mlock_limit_cur; 39 + static unsigned long mlock_limit_max; 40 + 41 + static int memfd_secret(unsigned int flags) 42 + { 43 + return syscall(__NR_memfd_secret, flags); 44 + } 45 + 46 + static void test_file_apis(int fd) 47 + { 48 + char buf[64]; 49 + 50 + if ((read(fd, buf, sizeof(buf)) >= 0) || 51 + (write(fd, buf, sizeof(buf)) >= 0) || 52 + (pread(fd, buf, sizeof(buf), 0) >= 0) || 53 + (pwrite(fd, buf, sizeof(buf), 0) >= 0)) 54 + fail("unexpected file IO\n"); 55 + else 56 + pass("file IO is blocked as expected\n"); 57 + } 58 + 59 + static void test_mlock_limit(int fd) 60 + { 61 + size_t len; 62 + char *mem; 63 + 64 + len = mlock_limit_cur; 65 + mem = mmap(NULL, len, prot, mode, fd, 0); 66 + if (mem == MAP_FAILED) { 67 + fail("unable to mmap secret memory\n"); 68 + return; 69 + } 70 + munmap(mem, len); 71 + 72 + len = mlock_limit_max * 2; 73 + mem = mmap(NULL, len, prot, mode, fd, 0); 74 + if (mem != MAP_FAILED) { 75 + fail("unexpected mlock limit violation\n"); 76 + munmap(mem, len); 77 + return; 78 + } 79 + 80 + pass("mlock limit is respected\n"); 81 + } 82 + 83 + static void try_process_vm_read(int fd, int pipefd[2]) 84 + { 85 + struct iovec liov, riov; 86 + char buf[64]; 87 + char *mem; 88 + 89 + if (read(pipefd[0], &mem, sizeof(mem)) < 0) { 90 + fail("pipe write: %s\n", strerror(errno)); 91 + exit(KSFT_FAIL); 92 + } 93 + 94 + liov.iov_len = riov.iov_len = sizeof(buf); 95 + liov.iov_base = buf; 96 + riov.iov_base = mem; 97 + 98 + if (process_vm_readv(getppid(), &liov, 1, &riov, 1, 0) < 0) { 99 + if (errno == ENOSYS) 100 + exit(KSFT_SKIP); 101 + exit(KSFT_PASS); 102 + } 103 + 104 + exit(KSFT_FAIL); 105 + } 106 + 107 + static void try_ptrace(int fd, int pipefd[2]) 108 + { 109 + pid_t ppid = getppid(); 110 + int status; 111 + char *mem; 112 + long ret; 113 + 114 + if (read(pipefd[0], &mem, sizeof(mem)) < 0) { 115 + perror("pipe write"); 116 + exit(KSFT_FAIL); 117 + } 118 + 119 + ret = ptrace(PTRACE_ATTACH, ppid, 0, 0); 120 + if (ret) { 121 + perror("ptrace_attach"); 122 + exit(KSFT_FAIL); 123 + } 124 + 125 + ret = waitpid(ppid, &status, WUNTRACED); 126 + if ((ret != ppid) || !(WIFSTOPPED(status))) { 127 + fprintf(stderr, "weird waitppid result %ld stat %x\n", 128 + ret, status); 129 + exit(KSFT_FAIL); 130 + } 131 + 132 + if (ptrace(PTRACE_PEEKDATA, ppid, mem, 0)) 133 + exit(KSFT_PASS); 134 + 135 + exit(KSFT_FAIL); 136 + } 137 + 138 + static void check_child_status(pid_t pid, const char *name) 139 + { 140 + int status; 141 + 142 + waitpid(pid, &status, 0); 143 + 144 + if (WIFEXITED(status) && WEXITSTATUS(status) == KSFT_SKIP) { 145 + skip("%s is not supported\n", name); 146 + return; 147 + } 148 + 149 + if ((WIFEXITED(status) && WEXITSTATUS(status) == KSFT_PASS) || 150 + WIFSIGNALED(status)) { 151 + pass("%s is blocked as expected\n", name); 152 + return; 153 + } 154 + 155 + fail("%s: unexpected memory access\n", name); 156 + } 157 + 158 + static void test_remote_access(int fd, const char *name, 159 + void (*func)(int fd, int pipefd[2])) 160 + { 161 + int pipefd[2]; 162 + pid_t pid; 163 + char *mem; 164 + 165 + if (pipe(pipefd)) { 166 + fail("pipe failed: %s\n", strerror(errno)); 167 + return; 168 + } 169 + 170 + pid = fork(); 171 + if (pid < 0) { 172 + fail("fork failed: %s\n", strerror(errno)); 173 + return; 174 + } 175 + 176 + if (pid == 0) { 177 + func(fd, pipefd); 178 + return; 179 + } 180 + 181 + mem = mmap(NULL, page_size, prot, mode, fd, 0); 182 + if (mem == MAP_FAILED) { 183 + fail("Unable to mmap secret memory\n"); 184 + return; 185 + } 186 + 187 + ftruncate(fd, page_size); 188 + memset(mem, PATTERN, page_size); 189 + 190 + if (write(pipefd[1], &mem, sizeof(mem)) < 0) { 191 + fail("pipe write: %s\n", strerror(errno)); 192 + return; 193 + } 194 + 195 + check_child_status(pid, name); 196 + } 197 + 198 + static void test_process_vm_read(int fd) 199 + { 200 + test_remote_access(fd, "process_vm_read", try_process_vm_read); 201 + } 202 + 203 + static void test_ptrace(int fd) 204 + { 205 + test_remote_access(fd, "ptrace", try_ptrace); 206 + } 207 + 208 + static int set_cap_limits(rlim_t max) 209 + { 210 + struct rlimit new; 211 + cap_t cap = cap_init(); 212 + 213 + new.rlim_cur = max; 214 + new.rlim_max = max; 215 + if (setrlimit(RLIMIT_MEMLOCK, &new)) { 216 + perror("setrlimit() returns error"); 217 + return -1; 218 + } 219 + 220 + /* drop capabilities including CAP_IPC_LOCK */ 221 + if (cap_set_proc(cap)) { 222 + perror("cap_set_proc() returns error"); 223 + return -2; 224 + } 225 + 226 + return 0; 227 + } 228 + 229 + static void prepare(void) 230 + { 231 + struct rlimit rlim; 232 + 233 + page_size = sysconf(_SC_PAGE_SIZE); 234 + if (!page_size) 235 + ksft_exit_fail_msg("Failed to get page size %s\n", 236 + strerror(errno)); 237 + 238 + if (getrlimit(RLIMIT_MEMLOCK, &rlim)) 239 + ksft_exit_fail_msg("Unable to detect mlock limit: %s\n", 240 + strerror(errno)); 241 + 242 + mlock_limit_cur = rlim.rlim_cur; 243 + mlock_limit_max = rlim.rlim_max; 244 + 245 + printf("page_size: %ld, mlock.soft: %ld, mlock.hard: %ld\n", 246 + page_size, mlock_limit_cur, mlock_limit_max); 247 + 248 + if (page_size > mlock_limit_cur) 249 + mlock_limit_cur = page_size; 250 + if (page_size > mlock_limit_max) 251 + mlock_limit_max = page_size; 252 + 253 + if (set_cap_limits(mlock_limit_max)) 254 + ksft_exit_fail_msg("Unable to set mlock limit: %s\n", 255 + strerror(errno)); 256 + } 257 + 258 + #define NUM_TESTS 4 259 + 260 + int main(int argc, char *argv[]) 261 + { 262 + int fd; 263 + 264 + prepare(); 265 + 266 + ksft_print_header(); 267 + ksft_set_plan(NUM_TESTS); 268 + 269 + fd = memfd_secret(0); 270 + if (fd < 0) { 271 + if (errno == ENOSYS) 272 + ksft_exit_skip("memfd_secret is not supported\n"); 273 + else 274 + ksft_exit_fail_msg("memfd_secret failed: %s\n", 275 + strerror(errno)); 276 + } 277 + 278 + test_mlock_limit(fd); 279 + test_file_apis(fd); 280 + test_process_vm_read(fd); 281 + test_ptrace(fd); 282 + 283 + close(fd); 284 + 285 + ksft_exit(!ksft_get_fail_cnt()); 286 + } 287 + 288 + #else /* __NR_memfd_secret */ 289 + 290 + int main(int argc, char *argv[]) 291 + { 292 + printf("skip: skipping memfd_secret test (missing __NR_memfd_secret)\n"); 293 + return KSFT_SKIP; 294 + } 295 + 296 + #endif /* __NR_memfd_secret */
+17
tools/testing/selftests/vm/run_vmtests.sh
··· 362 362 exitcode=1 363 363 fi 364 364 365 + echo "running memfd_secret test" 366 + echo "------------------------------------" 367 + ./memfd_secret 368 + ret_val=$? 369 + 370 + if [ $ret_val -eq 0 ]; then 371 + echo "[PASS]" 372 + elif [ $ret_val -eq $ksft_skip ]; then 373 + echo "[SKIP]" 374 + exitcode=$ksft_skip 375 + else 376 + echo "[FAIL]" 377 + exitcode=1 378 + fi 379 + 380 + exit $exitcode 381 + 365 382 exit $exitcode