Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021, Andrew Kaster <akaster@serenityos.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <AK/Assertions.h>
9#include <AK/Types.h>
10#include <LibCore/DeprecatedFile.h>
11#include <LibTest/TestCase.h>
12#include <fcntl.h>
13#include <stdio.h>
14#include <string.h>
15#include <sys/mman.h>
16#include <sys/stat.h>
17#include <sys/uio.h>
18#include <sys/wait.h>
19#include <unistd.h>
20
21#define EXPECT_ERROR_2(err, syscall, arg1, arg2) \
22 do { \
23 rc = syscall(arg1, arg2); \
24 EXPECT(rc < 0); \
25 EXPECT_EQ(errno, err); \
26 if (rc >= 0 || errno != err) { \
27 warnln(__FILE__ ":{}: Expected " #err ": " #syscall "({}, {}), got rc={}, errno={}", __LINE__, arg1, arg2, rc, errno); \
28 } \
29 } while (0)
30
31#define EXPECT_ERROR_3(err, syscall, arg1, arg2, arg3) \
32 do { \
33 rc = syscall(arg1, arg2, arg3); \
34 EXPECT(rc < 0); \
35 EXPECT_EQ(errno, err); \
36 if (rc >= 0 || errno != err) { \
37 warnln(__FILE__ ":{}: Expected " #err ": " #syscall "({}, {}, {}), got rc={}, errno={}", __LINE__, arg1, arg2, arg3, rc, errno); \
38 } \
39 } while (0)
40
41TEST_CASE(read_from_directory)
42{
43 char buffer[BUFSIZ];
44 int fd = open("/", O_DIRECTORY | O_RDONLY);
45 VERIFY(fd >= 0);
46 int rc;
47 EXPECT_ERROR_3(EISDIR, read, fd, buffer, sizeof(buffer));
48 rc = close(fd);
49 VERIFY(rc == 0);
50}
51
52TEST_CASE(write_to_directory)
53{
54 char str[] = "oh frick";
55 int fd = open("/", O_DIRECTORY | O_RDONLY);
56 if (fd < 0)
57 perror("open");
58 VERIFY(fd >= 0);
59 int rc;
60 EXPECT_ERROR_3(EBADF, write, fd, str, sizeof(str));
61 rc = close(fd);
62 VERIFY(rc == 0);
63}
64
65TEST_CASE(read_from_writeonly)
66{
67 char buffer[BUFSIZ];
68 int fd = open("/tmp/xxxx123", O_CREAT | O_WRONLY);
69 VERIFY(fd >= 0);
70 int rc;
71 EXPECT_ERROR_3(EBADF, read, fd, buffer, sizeof(buffer));
72 rc = close(fd);
73 VERIFY(rc == 0);
74 rc = unlink("/tmp/xxxx123");
75 VERIFY(rc == 0);
76}
77
78TEST_CASE(write_to_readonly)
79{
80 char str[] = "hello";
81 int fd = open("/tmp/abcd123", O_CREAT | O_RDONLY);
82 VERIFY(fd >= 0);
83 int rc;
84 EXPECT_ERROR_3(EBADF, write, fd, str, sizeof(str));
85 rc = close(fd);
86 VERIFY(rc == 0);
87 rc = unlink("/tmp/abcd123");
88 VERIFY(rc == 0);
89}
90
91TEST_CASE(read_past_eof)
92{
93 char buffer[BUFSIZ];
94 int fd = open("/home/anon/README.md", O_RDONLY);
95 if (fd < 0)
96 perror("open");
97 VERIFY(fd >= 0);
98 int rc;
99 rc = lseek(fd, 99999, SEEK_SET);
100 if (rc < 0)
101 perror("lseek");
102 rc = read(fd, buffer, sizeof(buffer));
103 if (rc < 0)
104 perror("read");
105 if (rc > 0)
106 warnln("read {} bytes past EOF", rc);
107 rc = close(fd);
108 VERIFY(rc == 0);
109}
110
111TEST_CASE(ftruncate_readonly)
112{
113 int fd = open("/tmp/trunctest", O_RDONLY | O_CREAT, 0666);
114 VERIFY(fd >= 0);
115 int rc;
116 EXPECT_ERROR_2(EBADF, ftruncate, fd, 0);
117 rc = close(fd);
118 VERIFY(rc == 0);
119 rc = unlink("/tmp/trunctest");
120 VERIFY(rc == 0);
121}
122
123TEST_CASE(ftruncate_negative)
124{
125 int fd = open("/tmp/trunctest", O_RDWR | O_CREAT, 0666);
126 VERIFY(fd >= 0);
127 int rc;
128 EXPECT_ERROR_2(EINVAL, ftruncate, fd, -1);
129 rc = close(fd);
130 VERIFY(rc == 0);
131 rc = unlink("/tmp/trunctest");
132 VERIFY(rc == 0);
133}
134
135TEST_CASE(mmap_directory)
136{
137 int fd = open("/tmp", O_RDONLY | O_DIRECTORY);
138 VERIFY(fd >= 0);
139 auto* ptr = mmap(nullptr, 4096, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0);
140 EXPECT_EQ(ptr, MAP_FAILED);
141 if (ptr != MAP_FAILED) {
142 warnln("Boo! mmap() of a directory succeeded!");
143 }
144 EXPECT_EQ(errno, ENODEV);
145 if (errno != ENODEV) {
146 warnln("Boo! mmap() of a directory gave errno={} instead of ENODEV!", errno);
147 return;
148 }
149 close(fd);
150}
151
152TEST_CASE(tmpfs_read_past_end)
153{
154 int fd = open("/tmp/x", O_RDWR | O_CREAT | O_TRUNC, 0600);
155 VERIFY(fd >= 0);
156
157 int rc = ftruncate(fd, 1);
158 VERIFY(rc == 0);
159
160 rc = lseek(fd, 4096, SEEK_SET);
161 VERIFY(rc == 4096);
162
163 char buffer[16];
164 int nread = read(fd, buffer, sizeof(buffer));
165 if (nread != 0) {
166 warnln("Expected 0-length read past end of file in /tmp");
167 }
168 rc = close(fd);
169 VERIFY(rc == 0);
170 rc = unlink("/tmp/x");
171 VERIFY(rc == 0);
172}
173
174TEST_CASE(sysfs_read_past_uptime_end)
175{
176 int fd = open("/sys/kernel/uptime", O_RDONLY);
177 VERIFY(fd >= 0);
178
179 int rc = lseek(fd, 4096, SEEK_SET);
180 VERIFY(rc == 4096);
181
182 char buffer[16];
183 int nread = read(fd, buffer, sizeof(buffer));
184 if (nread != 0) {
185 warnln("Expected 0-length read past end of file in /proc");
186 }
187 close(fd);
188}
189
190TEST_CASE(open_create_device)
191{
192 int fd = open("/tmp/fakedevice", (O_RDWR | O_CREAT), (S_IFCHR | 0600));
193 VERIFY(fd >= 0);
194
195 struct stat st;
196 int rc = fstat(fd, &st);
197 EXPECT(rc >= 0);
198 if (rc < 0) {
199 perror("stat");
200 }
201
202 EXPECT_EQ(st.st_mode, 0100600);
203 if (st.st_mode != 0100600) {
204 warnln("Expected mode 0100600 after attempt to create a device node with open(O_CREAT), mode={:o}", st.st_mode);
205 }
206 rc = unlink("/tmp/fakedevice");
207 EXPECT_EQ(rc, 0);
208 close(fd);
209 EXPECT_EQ(rc, 0);
210}
211
212TEST_CASE(unlink_symlink)
213{
214 int rc = symlink("/proc/2/foo", "/tmp/linky");
215 EXPECT(rc >= 0);
216 if (rc < 0) {
217 perror("symlink");
218 }
219
220 auto target_or_error = Core::DeprecatedFile::read_link("/tmp/linky");
221 EXPECT(!target_or_error.is_error());
222
223 auto target = target_or_error.release_value();
224 EXPECT_EQ(target, "/proc/2/foo");
225
226 rc = unlink("/tmp/linky");
227 EXPECT(rc >= 0);
228 if (rc < 0) {
229 perror("unlink");
230 warnln("Expected unlink() of a symlink into an unreadable directory to succeed!");
231 }
232}
233
234TEST_CASE(tmpfs_eoverflow)
235{
236 int fd = open("/tmp/x", O_RDWR | O_CREAT);
237 EXPECT(fd >= 0);
238
239 off_t rc = lseek(fd, INT64_MAX, SEEK_SET);
240 EXPECT_EQ(rc, INT64_MAX);
241
242 char buffer[16] {};
243 char empty_buffer[16] {};
244
245 rc = read(fd, buffer, sizeof(buffer));
246 EXPECT_EQ(rc, -1);
247 EXPECT_EQ(errno, EOVERFLOW);
248
249 [[maybe_unused]] auto ignored = strlcpy(buffer, "abcdefghijklmno", sizeof(buffer) - 1);
250
251 rc = write(fd, buffer, sizeof(buffer));
252 EXPECT_EQ(rc, -1);
253 EXPECT_EQ(errno, EOVERFLOW);
254 if (rc >= 0 || errno != EOVERFLOW) {
255 warnln("Expected EOVERFLOW when trying to write past INT64_MAX");
256 }
257
258 // ok now, write something to it, and try again
259 rc = lseek(fd, 0, SEEK_SET);
260 EXPECT_EQ(rc, 0);
261
262 rc = write(fd, buffer, sizeof(buffer));
263 EXPECT_EQ(rc, 16);
264
265 rc = lseek(fd, INT64_MAX, SEEK_SET);
266 EXPECT_EQ(rc, INT64_MAX);
267
268 memset(buffer, 0, sizeof(buffer));
269 rc = read(fd, buffer, sizeof(buffer));
270 EXPECT_EQ(rc, -1);
271 EXPECT_EQ(errno, EOVERFLOW);
272 if (rc >= 0 || errno != EOVERFLOW) {
273 warnln("Expected EOVERFLOW when trying to read past INT64_MAX");
274 }
275 EXPECT_EQ(0, memcmp(buffer, empty_buffer, sizeof(buffer)));
276
277 rc = close(fd);
278 EXPECT_EQ(rc, 0);
279 rc = unlink("/tmp/x");
280 EXPECT_EQ(rc, 0);
281}
282
283TEST_CASE(tmpfs_massive_file)
284{
285 int fd = open("/tmp/x", O_RDWR | O_CREAT);
286 EXPECT(fd >= 0);
287
288 off_t rc = lseek(fd, INT32_MAX, SEEK_SET);
289 EXPECT_EQ(rc, INT32_MAX);
290
291 char buffer[16] {};
292 rc = read(fd, buffer, sizeof(buffer));
293 EXPECT_EQ(rc, 0);
294
295 [[maybe_unused]] auto ignored = strlcpy(buffer, "abcdefghijklmno", sizeof(buffer) - 1);
296
297 rc = write(fd, buffer, sizeof(buffer));
298 EXPECT_EQ(rc, 16);
299
300 // ok now, write something to it, and try again
301 rc = lseek(fd, 0, SEEK_SET);
302 EXPECT_EQ(rc, 0);
303
304 rc = write(fd, buffer, sizeof(buffer));
305 EXPECT_EQ(rc, 16);
306
307 rc = lseek(fd, INT32_MAX, SEEK_SET);
308 EXPECT_EQ(rc, INT32_MAX);
309
310 memset(buffer, 0, sizeof(buffer));
311 rc = read(fd, buffer, sizeof(buffer));
312 EXPECT_EQ(rc, 16);
313 EXPECT(buffer != "abcdefghijklmno"sv);
314
315 rc = close(fd);
316 EXPECT_EQ(rc, 0);
317 rc = unlink("/tmp/x");
318 EXPECT_EQ(rc, 0);
319}
320
321TEST_CASE(rmdir_dot)
322{
323 int rc = mkdir("/home/anon/rmdir-test-1", 0700);
324 EXPECT_EQ(rc, 0);
325
326 rc = rmdir("/home/anon/rmdir-test-1/.");
327 EXPECT_NE(rc, 0);
328 EXPECT_EQ(errno, EINVAL);
329
330 rc = chdir("/home/anon/rmdir-test-1");
331 EXPECT_EQ(rc, 0);
332
333 rc = rmdir(".");
334 VERIFY(rc != 0);
335 EXPECT_EQ(errno, EINVAL);
336
337 rc = rmdir("/home/anon/rmdir-test-1");
338 EXPECT_EQ(rc, 0);
339}
340
341TEST_CASE(rmdir_dot_dot)
342{
343 int rc = mkdir("/home/anon/rmdir-test-2", 0700);
344 EXPECT_EQ(rc, 0);
345
346 rc = mkdir("/home/anon/rmdir-test-2/foo", 0700);
347 EXPECT_EQ(rc, 0);
348
349 rc = rmdir("/home/anon/rmdir-test-2/foo/..");
350 EXPECT_NE(rc, 0);
351 EXPECT_EQ(errno, ENOTEMPTY);
352
353 rc = rmdir("/home/anon/rmdir-test-2/foo");
354 EXPECT_EQ(rc, 0);
355
356 rc = rmdir("/home/anon/rmdir-test-2");
357 EXPECT_EQ(rc, 0);
358}
359
360TEST_CASE(rmdir_someone_elses_directory_in_my_sticky_directory)
361{
362 // NOTE: This test only works when run as root, since it has to chown a directory to someone else.
363 if (getuid() != 0)
364 return;
365
366 // Create /tmp/sticky-dir a sticky directory owned by 12345:12345
367 // Then, create /tmp/sticky-dir/notmine, a normal directory owned by 23456:23456
368 // Then, fork and seteuid to 12345, and try to rmdir the "notmine" directory. This should succeed.
369 // In the parent, waitpid on the child, and finally rmdir /tmp/sticky-dir
370
371 int rc = mkdir("/tmp/sticky-dir", 01777);
372 EXPECT_EQ(rc, 0);
373
374 rc = chown("/tmp/sticky-dir", 12345, 12345);
375 EXPECT_EQ(rc, 0);
376
377 rc = mkdir("/tmp/sticky-dir/notmine", 0700);
378 EXPECT_EQ(rc, 0);
379
380 rc = chown("/tmp/sticky-dir/notmine", 23456, 23456);
381 EXPECT_EQ(rc, 0);
382
383 int pid = fork();
384 EXPECT(pid >= 0);
385
386 if (pid == 0) {
387 // We are in the child.
388 rc = seteuid(12345);
389 EXPECT_EQ(rc, 0);
390
391 rc = rmdir("/tmp/sticky-dir/notmine");
392 EXPECT_EQ(rc, 0);
393 _exit(0);
394 }
395
396 int status = 0;
397 waitpid(pid, &status, 0);
398
399 rc = rmdir("/tmp/sticky-dir");
400 EXPECT_EQ(rc, 0);
401}
402
403TEST_CASE(rmdir_while_inside_dir)
404{
405 int rc = mkdir("/home/anon/testdir", 0700);
406 VERIFY(rc == 0);
407
408 rc = chdir("/home/anon/testdir");
409 VERIFY(rc == 0);
410
411 rc = rmdir("/home/anon/testdir");
412 VERIFY(rc == 0);
413
414 int fd = open("x", O_CREAT | O_RDWR, 0600);
415 EXPECT(fd < 0);
416 EXPECT_EQ(errno, ENOENT);
417 if (fd >= 0 || errno != ENOENT) {
418 warnln("Expected ENOENT when trying to create a file inside a deleted directory. Got {} with errno={}", fd, errno);
419 }
420
421 rc = chdir("/home/anon");
422 VERIFY(rc == 0);
423}
424
425TEST_CASE(writev)
426{
427 int pipefds[2];
428 int rc = pipe(pipefds);
429 EXPECT(rc == 0);
430
431 iovec iov[2];
432 iov[0].iov_base = const_cast<void*>((void const*)"Hello");
433 iov[0].iov_len = 5;
434 iov[1].iov_base = const_cast<void*>((void const*)"Friends");
435 iov[1].iov_len = 7;
436 int nwritten = writev(pipefds[1], iov, 2);
437 EXPECT_EQ(nwritten, 12);
438 if (nwritten < 0) {
439 perror("writev");
440 }
441 if (nwritten != 12) {
442 warnln("Didn't write 12 bytes to pipe with writev");
443 }
444
445 char buffer[32] {};
446 int nread = read(pipefds[0], buffer, sizeof(buffer));
447 EXPECT_EQ(nread, 12);
448 EXPECT_EQ(buffer, "HelloFriends"sv);
449 if (nread != 12 || memcmp(buffer, "HelloFriends", 12)) {
450 warnln("Didn't read the expected data from pipe after writev");
451 VERIFY_NOT_REACHED();
452 }
453
454 close(pipefds[0]);
455 close(pipefds[1]);
456}
457
458TEST_CASE(rmdir_root)
459{
460 int rc = rmdir("/");
461 EXPECT_EQ(rc, -1);
462 EXPECT_EQ(errno, EBUSY);
463 if (rc != -1 || errno != EBUSY) {
464 warnln("rmdir(/) didn't fail with EBUSY");
465 }
466}
467
468TEST_CASE(open_silly_things)
469{
470 int rc = -1;
471 EXPECT_ERROR_2(ENOTDIR, open, "/dev/zero", (O_DIRECTORY | O_RDONLY));
472 EXPECT_ERROR_2(EINVAL, open, "/dev/zero", (O_DIRECTORY | O_CREAT | O_RDWR));
473 EXPECT_ERROR_2(EEXIST, open, "/dev/zero", (O_CREAT | O_EXCL | O_RDWR));
474 EXPECT_ERROR_2(EINVAL, open, "/tmp/abcdef", (O_DIRECTORY | O_CREAT | O_RDWR));
475 EXPECT_ERROR_2(EACCES, open, "/sys/kernel/processes", (O_RDWR));
476 EXPECT_ERROR_2(ENOENT, open, "/boof/baaf/nonexistent", (O_CREAT | O_RDWR));
477 EXPECT_ERROR_2(EISDIR, open, "/tmp", (O_DIRECTORY | O_RDWR));
478 EXPECT_ERROR_2(EPERM, link, "/", "/home/anon/lolroot");
479}