1//===-- Unittests for mlock -----------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "src/__support/OSUtil/syscall.h" // For internal syscall function.
10#include "src/errno/libc_errno.h"
11#include "src/sys/mman/madvise.h"
12#include "src/sys/mman/mincore.h"
13#include "src/sys/mman/mlock.h"
14#include "src/sys/mman/mlock2.h"
15#include "src/sys/mman/mlockall.h"
16#include "src/sys/mman/mmap.h"
17#include "src/sys/mman/munlock.h"
18#include "src/sys/mman/munlockall.h"
19#include "src/sys/mman/munmap.h"
20#include "src/sys/resource/getrlimit.h"
21#include "src/unistd/sysconf.h"
22#include "test/UnitTest/ErrnoSetterMatcher.h"
23#include "test/UnitTest/LibcTest.h"
24#include "test/UnitTest/Test.h"
25
26#include <asm-generic/errno-base.h>
27#include <asm-generic/mman.h>
28#include <linux/capability.h>
29#include <sys/mman.h>
30#include <sys/resource.h>
31#include <sys/syscall.h>
32#include <unistd.h>
33
34using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher;
35
36struct PageHolder {
37 size_t size;
38 void *addr;
39
40 PageHolder()
41 : size(LIBC_NAMESPACE::sysconf(_SC_PAGESIZE)),
42 addr(LIBC_NAMESPACE::mmap(addr: nullptr, size, PROT_READ | PROT_WRITE,
43 MAP_ANONYMOUS | MAP_PRIVATE, fd: -1, offset: 0)) {}
44 ~PageHolder() {
45 if (addr != MAP_FAILED)
46 LIBC_NAMESPACE::munmap(addr, size);
47 }
48
49 char &operator[](size_t i) { return reinterpret_cast<char *>(addr)[i]; }
50
51 bool is_valid() { return addr != MAP_FAILED; }
52};
53
54static bool get_capacity(unsigned int cap) {
55 __user_cap_header_struct header;
56 header.pid = 0;
57 header.version = _LINUX_CAPABILITY_VERSION_3;
58 __user_cap_data_struct data[_LINUX_CAPABILITY_U32S_3];
59 // TODO: use capget wrapper once implemented.
60 // https://github.com/llvm/llvm-project/issues/80037
61 long res = LIBC_NAMESPACE::syscall_impl(
62 SYS_capget, LIBC_NAMESPACE::cpp::bit_cast<long>(from: &header),
63 LIBC_NAMESPACE::cpp::bit_cast<long>(from: &data));
64 if (res < 0)
65 return false;
66 unsigned idx = CAP_TO_INDEX(cap);
67 unsigned shift = CAP_TO_MASK(cap);
68 return (data[idx].effective & shift) != 0;
69}
70
71static bool is_permitted_size(size_t size) {
72 rlimit rlimits;
73 LIBC_NAMESPACE::getrlimit(RLIMIT_MEMLOCK, lim: &rlimits);
74 return size <= static_cast<size_t>(rlimits.rlim_cur) ||
75 get_capacity(CAP_IPC_LOCK);
76}
77
78TEST(LlvmLibcMlockTest, UnMappedMemory) {
79 EXPECT_THAT(LIBC_NAMESPACE::mlock(nullptr, 1024), Fails(ENOMEM));
80 EXPECT_THAT(LIBC_NAMESPACE::munlock(nullptr, 1024), Fails(ENOMEM));
81}
82
83TEST(LlvmLibcMlockTest, Overflow) {
84 PageHolder holder;
85 EXPECT_TRUE(holder.is_valid());
86 size_t negative_size = -holder.size;
87 int expected_errno = is_permitted_size(size: negative_size) ? EINVAL : ENOMEM;
88 EXPECT_THAT(LIBC_NAMESPACE::mlock(holder.addr, negative_size),
89 Fails(expected_errno));
90 EXPECT_THAT(LIBC_NAMESPACE::munlock(holder.addr, negative_size),
91 Fails(EINVAL));
92}
93
94#ifdef SYS_mlock2
95TEST(LlvmLibcMlockTest, MLock2) {
96 PageHolder holder;
97 EXPECT_TRUE(holder.is_valid());
98 EXPECT_THAT(LIBC_NAMESPACE::madvise(holder.addr, holder.size, MADV_DONTNEED),
99 Succeeds());
100 EXPECT_THAT(LIBC_NAMESPACE::mlock2(holder.addr, holder.size, 0), Succeeds());
101 unsigned char vec;
102 EXPECT_THAT(LIBC_NAMESPACE::mincore(holder.addr, holder.size, &vec),
103 Succeeds());
104 EXPECT_EQ(vec & 1, 1);
105 EXPECT_THAT(LIBC_NAMESPACE::munlock(holder.addr, holder.size), Succeeds());
106 EXPECT_THAT(LIBC_NAMESPACE::madvise(holder.addr, holder.size, MADV_DONTNEED),
107 Succeeds());
108 EXPECT_THAT(LIBC_NAMESPACE::mlock2(holder.addr, holder.size, MLOCK_ONFAULT),
109 Succeeds());
110 EXPECT_THAT(LIBC_NAMESPACE::mincore(holder.addr, holder.size, &vec),
111 Succeeds());
112 EXPECT_EQ(vec & 1, 0);
113 holder[0] = 1;
114 EXPECT_THAT(LIBC_NAMESPACE::mincore(holder.addr, holder.size, &vec),
115 Succeeds());
116 EXPECT_EQ(vec & 1, 1);
117 EXPECT_THAT(LIBC_NAMESPACE::munlock(holder.addr, holder.size), Succeeds());
118}
119#endif
120
121TEST(LlvmLibcMlockTest, InvalidFlag) {
122 size_t alloc_size = 128; // page size
123 LIBC_NAMESPACE::libc_errno = 0;
124 void *addr = LIBC_NAMESPACE::mmap(addr: nullptr, size: alloc_size, PROT_READ,
125 MAP_ANONYMOUS | MAP_PRIVATE, fd: -1, offset: 0);
126 ASSERT_ERRNO_SUCCESS();
127 EXPECT_NE(addr, MAP_FAILED);
128
129 // Invalid mlock2 flags.
130 EXPECT_THAT(LIBC_NAMESPACE::mlock2(addr, alloc_size, 1234), Fails(EINVAL));
131
132 // Invalid mlockall flags.
133 EXPECT_THAT(LIBC_NAMESPACE::mlockall(1234), Fails(EINVAL));
134
135 // man 2 mlockall says EINVAL is a valid return code when MCL_ONFAULT was
136 // specified without MCL_FUTURE or MCL_CURRENT, but this seems to fail on
137 // Linux 4.19.y (EOL).
138 // TODO(ndesaulniers) re-enable after
139 // https://github.com/llvm/llvm-project/issues/80073 is fixed.
140 // EXPECT_THAT(LIBC_NAMESPACE::mlockall(MCL_ONFAULT), Fails(EINVAL));
141
142 LIBC_NAMESPACE::munmap(addr, size: alloc_size);
143}
144
145TEST(LlvmLibcMlockTest, MLockAll) {
146 {
147 PageHolder holder;
148 EXPECT_TRUE(holder.is_valid());
149 EXPECT_THAT(
150 LIBC_NAMESPACE::madvise(holder.addr, holder.size, MADV_DONTNEED),
151 Succeeds());
152 auto retval = LIBC_NAMESPACE::mlockall(MCL_CURRENT);
153 if (retval == -1) {
154 EXPECT_TRUE(LIBC_NAMESPACE::libc_errno == ENOMEM ||
155 LIBC_NAMESPACE::libc_errno == EPERM);
156 LIBC_NAMESPACE::libc_errno = 0;
157 return;
158 }
159 unsigned char vec;
160 EXPECT_THAT(LIBC_NAMESPACE::mincore(holder.addr, holder.size, &vec),
161 Succeeds());
162 EXPECT_EQ(vec & 1, 1);
163 EXPECT_THAT(LIBC_NAMESPACE::munlockall(), Succeeds());
164 }
165 {
166 auto retval = LIBC_NAMESPACE::mlockall(MCL_FUTURE);
167 if (retval == -1) {
168 EXPECT_TRUE(LIBC_NAMESPACE::libc_errno == ENOMEM ||
169 LIBC_NAMESPACE::libc_errno == EPERM);
170 LIBC_NAMESPACE::libc_errno = 0;
171 return;
172 }
173 PageHolder holder;
174 EXPECT_TRUE(holder.is_valid());
175 unsigned char vec;
176 EXPECT_THAT(LIBC_NAMESPACE::mincore(holder.addr, holder.size, &vec),
177 Succeeds());
178 EXPECT_EQ(vec & 1, 1);
179 EXPECT_THAT(LIBC_NAMESPACE::munlockall(), Succeeds());
180 }
181#ifdef MCL_ONFAULT
182 {
183 auto retval = LIBC_NAMESPACE::mlockall(MCL_FUTURE | MCL_ONFAULT);
184 if (retval == -1) {
185 EXPECT_TRUE(LIBC_NAMESPACE::libc_errno == ENOMEM ||
186 LIBC_NAMESPACE::libc_errno == EPERM);
187 LIBC_NAMESPACE::libc_errno = 0;
188 return;
189 }
190 PageHolder holder;
191 EXPECT_TRUE(holder.is_valid());
192 unsigned char vec;
193 EXPECT_THAT(LIBC_NAMESPACE::mincore(holder.addr, holder.size, &vec),
194 Succeeds());
195 EXPECT_EQ(vec & 1, 0);
196 holder[0] = 1;
197 EXPECT_THAT(LIBC_NAMESPACE::mincore(holder.addr, holder.size, &vec),
198 Succeeds());
199 EXPECT_EQ(vec & 1, 1);
200 EXPECT_THAT(LIBC_NAMESPACE::munlockall(), Succeeds());
201 }
202#endif
203}
204

source code of libc/test/src/sys/mman/linux/mlock_test.cpp