1272343Sngie/* $NetBSD: t_mlock.c,v 1.5 2014/02/26 20:49:26 martin Exp $ */ 2272343Sngie 3272343Sngie/*- 4272343Sngie * Copyright (c) 2012 The NetBSD Foundation, Inc. 5272343Sngie * All rights reserved. 6272343Sngie * 7272343Sngie * This code is derived from software contributed to The NetBSD Foundation 8272343Sngie * by Jukka Ruohonen. 9272343Sngie * 10272343Sngie * Redistribution and use in source and binary forms, with or without 11272343Sngie * modification, are permitted provided that the following conditions 12272343Sngie * are met: 13272343Sngie * 1. Redistributions of source code must retain the above copyright 14272343Sngie * notice, this list of conditions and the following disclaimer. 15272343Sngie * 2. Redistributions in binary form must reproduce the above copyright 16272343Sngie * notice, this list of conditions and the following disclaimer in the 17272343Sngie * documentation and/or other materials provided with the distribution. 18272343Sngie * 19272343Sngie * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20272343Sngie * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21272343Sngie * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22272343Sngie * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23272343Sngie * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24272343Sngie * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25272343Sngie * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26272343Sngie * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27272343Sngie * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28272343Sngie * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29272343Sngie * POSSIBILITY OF SUCH DAMAGE. 30272343Sngie */ 31272343Sngie#include <sys/cdefs.h> 32272343Sngie__RCSID("$NetBSD: t_mlock.c,v 1.5 2014/02/26 20:49:26 martin Exp $"); 33272343Sngie 34273592Sngie#ifdef __FreeBSD__ 35273592Sngie#include <sys/types.h> 36273592Sngie#endif 37272343Sngie#include <sys/mman.h> 38272343Sngie#include <sys/resource.h> 39272343Sngie#include <sys/sysctl.h> 40272343Sngie#include <sys/wait.h> 41272343Sngie 42272343Sngie#include <errno.h> 43272343Sngie#include <atf-c.h> 44272343Sngie#include <stdint.h> 45272343Sngie#include <stdio.h> 46272343Sngie#include <stdlib.h> 47272343Sngie#include <unistd.h> 48272343Sngie 49273592Sngie#ifdef __FreeBSD__ 50294894Sngie#include <limits.h> 51273592Sngie#define _KMEMUSER 52273592Sngie#include <machine/vmparam.h> 53273592Sngie#endif 54273592Sngie 55272343Sngiestatic long page = 0; 56272343Sngie 57294894Sngie#ifdef __FreeBSD__ 58294894Sngie#define VM_MAX_WIRED "vm.max_wired" 59294894Sngie 60294894Sngiestatic void 61294894Sngievm_max_wired_sysctl(int *old_value, int *new_value) 62294894Sngie{ 63294894Sngie size_t old_len; 64294894Sngie size_t new_len = (new_value == NULL ? 0 : sizeof(int)); 65294894Sngie 66294894Sngie if (old_value == NULL) 67294894Sngie printf("Setting the new value to %d\n", *new_value); 68294894Sngie else { 69294894Sngie ATF_REQUIRE_MSG(sysctlbyname(VM_MAX_WIRED, NULL, &old_len, 70294894Sngie new_value, new_len) == 0, 71294894Sngie "sysctlbyname(%s) failed: %s", VM_MAX_WIRED, strerror(errno)); 72294894Sngie } 73294894Sngie 74294894Sngie ATF_REQUIRE_MSG(sysctlbyname(VM_MAX_WIRED, old_value, &old_len, 75294894Sngie new_value, new_len) == 0, 76294894Sngie "sysctlbyname(%s) failed: %s", VM_MAX_WIRED, strerror(errno)); 77294894Sngie 78294894Sngie if (old_value != NULL) 79294894Sngie printf("Saved the old value (%d)\n", *old_value); 80294894Sngie} 81294894Sngie 82294894Sngiestatic void 83294894Sngieset_vm_max_wired(int new_value) 84294894Sngie{ 85294894Sngie FILE *fp; 86294894Sngie int old_value; 87294894Sngie 88294894Sngie fp = fopen(VM_MAX_WIRED, "w"); 89294894Sngie if (fp == NULL) { 90294894Sngie atf_tc_skip("could not open %s for writing: %s", 91294894Sngie VM_MAX_WIRED, strerror(errno)); 92294894Sngie return; 93294894Sngie } 94294894Sngie 95294894Sngie vm_max_wired_sysctl(&old_value, NULL); 96294894Sngie 97294894Sngie ATF_REQUIRE_MSG(fprintf(fp, "%d", old_value) > 0, 98294894Sngie "saving %s failed", VM_MAX_WIRED); 99294894Sngie 100294894Sngie fclose(fp); 101294894Sngie 102294894Sngie vm_max_wired_sysctl(NULL, &new_value); 103294894Sngie} 104294894Sngie 105294894Sngiestatic void 106294894Sngierestore_vm_max_wired(void) 107294894Sngie{ 108294894Sngie FILE *fp; 109294894Sngie int saved_max_wired; 110294894Sngie 111294894Sngie fp = fopen(VM_MAX_WIRED, "r"); 112294894Sngie if (fp == NULL) { 113294894Sngie perror("fopen failed\n"); 114294894Sngie return; 115294894Sngie } 116294894Sngie 117294894Sngie if (fscanf(fp, "%d", &saved_max_wired) != 1) { 118294894Sngie perror("fscanf failed\n"); 119294894Sngie fclose(fp); 120294894Sngie return; 121294894Sngie } 122294894Sngie 123294894Sngie fclose(fp); 124294894Sngie printf("old value in %s: %d\n", VM_MAX_WIRED, saved_max_wired); 125294894Sngie 126294894Sngie if (saved_max_wired == 0) /* This will cripple the test host */ 127294894Sngie return; 128294894Sngie 129294894Sngie vm_max_wired_sysctl(NULL, &saved_max_wired); 130294894Sngie} 131294894Sngie#endif 132294894Sngie 133272343SngieATF_TC(mlock_clip); 134272343SngieATF_TC_HEAD(mlock_clip, tc) 135272343Sngie{ 136272343Sngie atf_tc_set_md_var(tc, "descr", "Test with mlock(2) that UVM only " 137272343Sngie "clips if the clip address is within the entry (PR kern/44788)"); 138272343Sngie} 139272343Sngie 140272343SngieATF_TC_BODY(mlock_clip, tc) 141272343Sngie{ 142272343Sngie void *buf; 143272343Sngie 144272343Sngie buf = malloc(page); 145272343Sngie ATF_REQUIRE(buf != NULL); 146272343Sngie 147272343Sngie if (page < 1024) 148272343Sngie atf_tc_skip("page size too small"); 149272343Sngie 150272343Sngie for (size_t i = page; i >= 1; i = i - 1024) { 151272343Sngie (void)mlock(buf, page - i); 152272343Sngie (void)munlock(buf, page - i); 153272343Sngie } 154272343Sngie 155272343Sngie free(buf); 156272343Sngie} 157272343Sngie 158294894Sngie#ifdef __FreeBSD__ 159294894SngieATF_TC_WITH_CLEANUP(mlock_err); 160294894Sngie#else 161272343SngieATF_TC(mlock_err); 162294894Sngie#endif 163272343SngieATF_TC_HEAD(mlock_err, tc) 164272343Sngie{ 165272343Sngie atf_tc_set_md_var(tc, "descr", 166272343Sngie "Test error conditions in mlock(2) and munlock(2)"); 167294894Sngie#ifdef __FreeBSD__ 168294894Sngie atf_tc_set_md_var(tc, "require.config", "allow_sysctl_side_effects"); 169294894Sngie atf_tc_set_md_var(tc, "require.user", "root"); 170294894Sngie#endif 171272343Sngie} 172272343Sngie 173272343SngieATF_TC_BODY(mlock_err, tc) 174272343Sngie{ 175273592Sngie#ifdef __NetBSD__ 176272343Sngie unsigned long vmin = 0; 177272343Sngie size_t len = sizeof(vmin); 178273592Sngie#endif 179300680Sbr#if !defined(__aarch64__) && !defined(__riscv__) 180272343Sngie void *invalid_ptr; 181300303Sandrew#endif 182272343Sngie int null_errno = ENOMEM; /* error expected for NULL */ 183272343Sngie 184273592Sngie#ifdef __FreeBSD__ 185273592Sngie#ifdef VM_MIN_ADDRESS 186273592Sngie if ((uintptr_t)VM_MIN_ADDRESS > 0) 187273592Sngie null_errno = EINVAL; /* NULL is not inside user VM */ 188273592Sngie#endif 189294894Sngie /* Set max_wired really really high to avoid EAGAIN */ 190294894Sngie set_vm_max_wired(INT_MAX); 191273592Sngie#else 192272343Sngie if (sysctlbyname("vm.minaddress", &vmin, &len, NULL, 0) != 0) 193272343Sngie atf_tc_fail("failed to read vm.minaddress"); 194272343Sngie 195272343Sngie if (vmin > 0) 196272343Sngie null_errno = EINVAL; /* NULL is not inside user VM */ 197273592Sngie#endif 198272343Sngie 199272343Sngie errno = 0; 200272343Sngie ATF_REQUIRE_ERRNO(null_errno, mlock(NULL, page) == -1); 201272343Sngie 202272343Sngie errno = 0; 203272343Sngie ATF_REQUIRE_ERRNO(null_errno, mlock((char *)0, page) == -1); 204272343Sngie 205272343Sngie errno = 0; 206272343Sngie ATF_REQUIRE_ERRNO(EINVAL, mlock((char *)-1, page) == -1); 207272343Sngie 208272343Sngie errno = 0; 209272343Sngie ATF_REQUIRE_ERRNO(null_errno, munlock(NULL, page) == -1); 210272343Sngie 211272343Sngie errno = 0; 212272343Sngie ATF_REQUIRE_ERRNO(null_errno, munlock((char *)0, page) == -1); 213272343Sngie 214272343Sngie errno = 0; 215272343Sngie ATF_REQUIRE_ERRNO(EINVAL, munlock((char *)-1, page) == -1); 216272343Sngie 217300680Sbr/* There is no sbrk on AArch64 and RISC-V */ 218300680Sbr#if !defined(__aarch64__) && !defined(__riscv__) 219272343Sngie /* 220272343Sngie * Try to create a pointer to an unmapped page - first after current 221272343Sngie * brk will likely do. 222272343Sngie */ 223272343Sngie invalid_ptr = (void*)(((uintptr_t)sbrk(0)+page) & ~(page-1)); 224272343Sngie printf("testing with (hopefully) invalid pointer %p\n", invalid_ptr); 225272343Sngie 226272343Sngie errno = 0; 227272343Sngie ATF_REQUIRE_ERRNO(ENOMEM, mlock(invalid_ptr, page) == -1); 228272343Sngie 229272343Sngie errno = 0; 230272343Sngie ATF_REQUIRE_ERRNO(ENOMEM, munlock(invalid_ptr, page) == -1); 231300303Sandrew#endif 232272343Sngie} 233272343Sngie 234294894Sngie#ifdef __FreeBSD__ 235294894SngieATF_TC_CLEANUP(mlock_err, tc) 236294894Sngie{ 237294894Sngie 238294894Sngie restore_vm_max_wired(); 239294894Sngie} 240294894Sngie#endif 241294894Sngie 242272343SngieATF_TC(mlock_limits); 243272343SngieATF_TC_HEAD(mlock_limits, tc) 244272343Sngie{ 245272343Sngie atf_tc_set_md_var(tc, "descr", "Test system limits with mlock(2)"); 246272343Sngie} 247272343Sngie 248272343SngieATF_TC_BODY(mlock_limits, tc) 249272343Sngie{ 250272343Sngie struct rlimit res; 251272343Sngie void *buf; 252272343Sngie pid_t pid; 253272343Sngie int sta; 254272343Sngie 255272343Sngie buf = malloc(page); 256272343Sngie ATF_REQUIRE(buf != NULL); 257272343Sngie 258272343Sngie pid = fork(); 259272343Sngie ATF_REQUIRE(pid >= 0); 260272343Sngie 261272343Sngie if (pid == 0) { 262272343Sngie 263272343Sngie for (ssize_t i = page; i >= 2; i -= 100) { 264272343Sngie 265272343Sngie res.rlim_cur = i - 1; 266272343Sngie res.rlim_max = i - 1; 267272343Sngie 268272343Sngie (void)fprintf(stderr, "trying to lock %zd bytes " 269272343Sngie "with %zu byte limit\n", i, (size_t)res.rlim_cur); 270272343Sngie 271272343Sngie if (setrlimit(RLIMIT_MEMLOCK, &res) != 0) 272272343Sngie _exit(EXIT_FAILURE); 273272343Sngie 274272343Sngie errno = 0; 275272343Sngie 276273592Sngie#ifdef __FreeBSD__ 277273592Sngie /* 278273592Sngie * NetBSD doesn't conform to POSIX with ENOMEM requirement; 279273592Sngie * FreeBSD does. 280273592Sngie * 281273592Sngie * See: NetBSD PR # kern/48962 for more details. 282273592Sngie */ 283273592Sngie if (mlock(buf, i) != -1 || errno != ENOMEM) { 284273592Sngie#else 285272343Sngie if (mlock(buf, i) != -1 || errno != EAGAIN) { 286273592Sngie#endif 287272343Sngie (void)munlock(buf, i); 288272343Sngie _exit(EXIT_FAILURE); 289272343Sngie } 290272343Sngie } 291272343Sngie 292272343Sngie _exit(EXIT_SUCCESS); 293272343Sngie } 294272343Sngie 295272343Sngie (void)wait(&sta); 296272343Sngie 297272343Sngie if (WIFEXITED(sta) == 0 || WEXITSTATUS(sta) != EXIT_SUCCESS) 298272343Sngie atf_tc_fail("mlock(2) locked beyond system limits"); 299272343Sngie 300272343Sngie free(buf); 301272343Sngie} 302272343Sngie 303294894Sngie#ifdef __FreeBSD__ 304294894SngieATF_TC_WITH_CLEANUP(mlock_mmap); 305294894Sngie#else 306272343SngieATF_TC(mlock_mmap); 307294894Sngie#endif 308272343SngieATF_TC_HEAD(mlock_mmap, tc) 309272343Sngie{ 310272343Sngie atf_tc_set_md_var(tc, "descr", "Test mlock(2)-mmap(2) interaction"); 311294894Sngie#ifdef __FreeBSD__ 312294894Sngie atf_tc_set_md_var(tc, "require.config", "allow_sysctl_side_effects"); 313294894Sngie atf_tc_set_md_var(tc, "require.user", "root"); 314294894Sngie#endif 315272343Sngie} 316272343Sngie 317272343SngieATF_TC_BODY(mlock_mmap, tc) 318272343Sngie{ 319273592Sngie#ifdef __NetBSD__ 320272343Sngie static const int flags = MAP_ANON | MAP_PRIVATE | MAP_WIRED; 321273592Sngie#else 322273592Sngie static const int flags = MAP_ANON | MAP_PRIVATE; 323273592Sngie#endif 324272343Sngie void *buf; 325272343Sngie 326294894Sngie#ifdef __FreeBSD__ 327294894Sngie /* Set max_wired really really high to avoid EAGAIN */ 328294894Sngie set_vm_max_wired(INT_MAX); 329294894Sngie#endif 330294894Sngie 331272343Sngie /* 332272343Sngie * Make a wired RW mapping and check that mlock(2) 333272343Sngie * does not fail for the (already locked) mapping. 334272343Sngie */ 335272343Sngie buf = mmap(NULL, page, PROT_READ | PROT_WRITE, flags, -1, 0); 336272343Sngie 337272343Sngie ATF_REQUIRE(buf != MAP_FAILED); 338273592Sngie#ifdef __FreeBSD__ 339273592Sngie /* 340273592Sngie * The duplicate mlock call is added to ensure that the call works 341273592Sngie * as described above without MAP_WIRED support. 342273592Sngie */ 343272343Sngie ATF_REQUIRE(mlock(buf, page) == 0); 344273592Sngie#endif 345273592Sngie ATF_REQUIRE(mlock(buf, page) == 0); 346272343Sngie ATF_REQUIRE(munlock(buf, page) == 0); 347272343Sngie ATF_REQUIRE(munmap(buf, page) == 0); 348272343Sngie ATF_REQUIRE(munlock(buf, page) != 0); 349272343Sngie 350272343Sngie /* 351272343Sngie * But it should be impossible to mlock(2) a PROT_NONE mapping. 352272343Sngie */ 353272343Sngie buf = mmap(NULL, page, PROT_NONE, flags, -1, 0); 354272343Sngie 355272343Sngie ATF_REQUIRE(buf != MAP_FAILED); 356273592Sngie#ifdef __FreeBSD__ 357273592Sngie ATF_REQUIRE_ERRNO(ENOMEM, mlock(buf, page) != 0); 358273592Sngie#else 359272343Sngie ATF_REQUIRE(mlock(buf, page) != 0); 360273592Sngie#endif 361272343Sngie ATF_REQUIRE(munmap(buf, page) == 0); 362272343Sngie} 363272343Sngie 364294894Sngie#ifdef __FreeBSD__ 365294894SngieATF_TC_CLEANUP(mlock_mmap, tc) 366294894Sngie{ 367294894Sngie 368294894Sngie restore_vm_max_wired(); 369294894Sngie} 370294894Sngie#endif 371294894Sngie 372294894Sngie#ifdef __FreeBSD__ 373294894SngieATF_TC_WITH_CLEANUP(mlock_nested); 374294894Sngie#else 375272343SngieATF_TC(mlock_nested); 376294894Sngie#endif 377272343SngieATF_TC_HEAD(mlock_nested, tc) 378272343Sngie{ 379272343Sngie atf_tc_set_md_var(tc, "descr", 380272343Sngie "Test that consecutive mlock(2) calls succeed"); 381294894Sngie#ifdef __FreeBSD__ 382294894Sngie atf_tc_set_md_var(tc, "require.config", "allow_sysctl_side_effects"); 383294894Sngie atf_tc_set_md_var(tc, "require.user", "root"); 384294894Sngie#endif 385272343Sngie} 386272343Sngie 387272343SngieATF_TC_BODY(mlock_nested, tc) 388272343Sngie{ 389272343Sngie const size_t maxiter = 100; 390272343Sngie void *buf; 391272343Sngie 392294894Sngie#ifdef __FreeBSD__ 393294894Sngie /* Set max_wired really really high to avoid EAGAIN */ 394294894Sngie set_vm_max_wired(INT_MAX); 395294894Sngie#endif 396294894Sngie 397272343Sngie buf = malloc(page); 398272343Sngie ATF_REQUIRE(buf != NULL); 399272343Sngie 400272343Sngie for (size_t i = 0; i < maxiter; i++) 401272343Sngie ATF_REQUIRE(mlock(buf, page) == 0); 402272343Sngie 403272343Sngie ATF_REQUIRE(munlock(buf, page) == 0); 404272343Sngie free(buf); 405272343Sngie} 406272343Sngie 407294894Sngie#ifdef __FreeBSD__ 408294894SngieATF_TC_CLEANUP(mlock_nested, tc) 409294894Sngie{ 410294894Sngie 411294894Sngie restore_vm_max_wired(); 412294894Sngie} 413294894Sngie#endif 414294894Sngie 415272343SngieATF_TP_ADD_TCS(tp) 416272343Sngie{ 417272343Sngie 418272343Sngie page = sysconf(_SC_PAGESIZE); 419272343Sngie ATF_REQUIRE(page >= 0); 420272343Sngie 421272343Sngie ATF_TP_ADD_TC(tp, mlock_clip); 422272343Sngie ATF_TP_ADD_TC(tp, mlock_err); 423272343Sngie ATF_TP_ADD_TC(tp, mlock_limits); 424272343Sngie ATF_TP_ADD_TC(tp, mlock_mmap); 425272343Sngie ATF_TP_ADD_TC(tp, mlock_nested); 426272343Sngie 427272343Sngie return atf_no_error(); 428272343Sngie} 429