1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
| #![no_std]
#![no_main]
#[macro_use]
#[path = "ulib.rs"]
mod ulib;
extern crate alloc;
use alloc::string::String;
use alloc::vec;
use ulib::*;
const KERNEL_PTR: u64 = 0xffff_8000_0000_0000;
fn sys(nr: u64, a: u64, b: u64, c: u64) -> i64 {
syscall3(nr, a, b, c)
}
fn main() -> i64 {
let mut c = Checks::new("pathtest");
let mut st = Stat::default();
let stp = &mut st as *mut Stat as u64;
for &(bad, what) in &[(0u64, "NULL"), (KERNEL_PTR, "a kernel address"), (0xf0_0000_0000, "an unmapped user address")] {
let calls = [
("open", sys(SYS_OPEN, bad, 0, 0)),
("openat", syscall5(SYS_OPENAT, AT_FDCWD as u64, bad, 0, 0, 0)),
("stat", sys(SYS_STAT, bad, stp, 0)),
("lstat", sys(SYS_LSTAT, bad, stp, 0)),
("newfstatat", syscall5(SYS_NEWFSTATAT, AT_FDCWD as u64, bad, stp, 0, 0)),
("access", sys(SYS_ACCESS, bad, 0, 0)),
("mkdir", sys(SYS_MKDIR, bad, 0o755, 0)),
("rmdir", sys(SYS_RMDIR, bad, 0, 0)),
("unlink", sys(SYS_UNLINK, bad, 0, 0)),
("rename", sys(SYS_RENAME, bad, bad, 0)),
("chdir", sys(SYS_CHDIR, bad, 0, 0)),
("readlink", sys(SYS_READLINK, bad, stp, 64)),
("execve", sys(SYS_EXECVE, bad, 0, 0)),
];
let failed: alloc::vec::Vec<&str> = calls.iter().filter(|(_, r)| *r != -EFAULT).map(|(n, _)| *n).collect();
if !failed.is_empty() {
println!("pathtest: not EFAULT for {}: {:?}", what, failed);
}
c.check(failed.is_empty(), &alloc::format!("13 path calls with {} as the path: EFAULT", what));
}
let two = mmap(0, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) as u64;
munmap(two + 4096, 4096);
let tail = two + 4096 - 8;
unsafe { core::ptr::copy_nonoverlapping(b"/tmp/abc".as_ptr(), tail as *mut u8, 8) }; c.check(sys(SYS_STAT, tail, stp, 0) == -EFAULT, "a path running into an unmapped page: EFAULT");
unsafe { *((two + 4095) as *mut u8) = 0 }; c.check(sys(SYS_STAT, tail, stp, 0) == -ENOENT, "the same path ending right at the page end: ENOENT");
munmap(two, 4096);
c.check(sys(SYS_STAT, c"/".as_ptr() as u64, KERNEL_PTR, 0) == -EFAULT, "stat into a kernel address: EFAULT");
c.check(sys(SYS_FSTAT, 0, 0, 0) == -EFAULT, "fstat into NULL: EFAULT");
let mut long = vec![b'a'; 5000]; long.push(0);
c.check(sys(SYS_STAT, long.as_ptr() as u64, stp, 0) == -ENAMETOOLONG, "no NUL within PATH_MAX: ENAMETOOLONG");
let mut max = String::new();
while max.len() < 4095 {
max.push_str("/d");
}
max.truncate(4095);
c.check(stat(&max, &mut st) == -ENOENT, "a 4095-byte path is accepted (ENOENT)");
c.check(sys(SYS_STAT, c"".as_ptr() as u64, stp, 0) == -ENOENT, "an empty path: ENOENT");
c.check(core::mem::size_of::<Stat>() == 144, "struct stat is 144 bytes");
c.check(stat("/mnt/disk0/bin", &mut st) == 0 && st.is_dir() && st.nlink == 2, "a directory: S_IFDIR, 2 links");
let r = stat("/mnt/disk0/bin/pathtest", &mut st);
let ino = st.ino;
c.check(
r == 0 && st.is_file() && st.size > 0 && st.blksize == 4096 && st.blocks == (st.size + 511) / 512 && st.ino != 0,
"a file: S_IFREG, size, blksize 4096, 512-byte blocks, an inode number",
);
c.check(st.uid == 0 && st.gid == 0 && st.perm() != 0, "owned by root, with permission bits");
let fd = open("/mnt/disk0/bin/pathtest", O_RDONLY);
let mut st2 = Stat::default();
c.check(fstat(fd, &mut st2) == 0 && st2.ino == ino && st2.size == st.size, "fstat on it: same inode and size");
let mut st3 = Stat::default();
c.check(fstatat(fd, "", &mut st3, AT_EMPTY_PATH) == 0 && st3.ino == ino, "newfstatat(fd, \"\", AT_EMPTY_PATH) = fstat");
c.check(fstatat(fd, "x", &mut st3, 0) == -ENOTDIR, "a file as dirfd: ENOTDIR");
close(fd);
c.check(fstat(0, &mut st) == 0 && st.is_char_device() && isatty(0), "stdin: a character device and a tty");
let (r, w) = pipe().unwrap();
c.check(fstat(r, &mut st) == 0 && st.file_type() == S_IFIFO, "a pipe: S_IFIFO");
close(r);
close(w);
let link = (0..4).map(|i| alloc::format!("/mnt/disk{}/hello-link", i)).find(|p| lstat(p, &mut st) == 0);
if let Some(link) = link {
c.check(lstat(&link, &mut st) == 0 && st.is_symlink() && st.size == 9, "lstat on a symlink: S_IFLNK, size = target length");
c.check(stat(&link, &mut st) == 0 && st.is_file(), "stat on it: the file it points to");
let mut buf = [0u8; 64];
let n = readlink(&link, &mut buf);
c.check(n == 9 && &buf[..9] == b"hello.txt", "readlink: the target, no NUL");
let dir = &link[..link.rfind('/').unwrap()];
let dfd = open(dir, O_RDONLY | O_DIRECTORY);
let n = syscall5(SYS_READLINKAT, dfd as u64, c"hello-link".as_ptr() as u64, buf.as_mut_ptr() as u64, 64, 0);
c.check(n == 9, "readlinkat(dirfd, \"hello-link\")");
c.check(fstatat(dfd, "hello-link", &mut st, AT_SYMLINK_NOFOLLOW) == 0 && st.is_symlink(), "newfstatat AT_SYMLINK_NOFOLLOW = lstat");
close(dfd);
} else {
println!("pathtest: no ext2 disk with hello-link: skipped symlink checks");
}
c.check(readlink("/mnt/disk0/bin", &mut [0u8; 8]) == -EINVAL, "readlink on a non-link: EINVAL");
c.check(open("/mnt/disk0/bin/pathtest", O_RDONLY | O_DIRECTORY) == -ENOTDIR, "O_DIRECTORY on a file: ENOTDIR");
c.check(open("/tmp", O_WRONLY) == -EISDIR, "a directory opened for writing: EISDIR");
let fd = open("/tmp/pt-excl", O_WRONLY | O_CREAT | O_EXCL);
c.check(fd >= 0 && open("/tmp/pt-excl", O_WRONLY | O_CREAT | O_EXCL) == -EEXIST, "O_CREAT|O_EXCL: creates, then EEXIST");
close(fd);
c.check(access("/tmp/pt-excl") == 0 && access("/tmp/nope") == -ENOENT, "access: 0 / ENOENT");
c.check(mkdir("/tmp/ptdir") == 0, "mkdir /tmp/ptdir");
let dfd = open("/tmp/ptdir", O_RDONLY | O_DIRECTORY);
c.check(dfd >= 0, "open a directory with O_DIRECTORY");
let fd = openat(dfd, "inner.txt", O_WRONLY | O_CREAT);
c.check(fd >= 0 && write(fd, b"12345") == 5, "openat(dirfd, \"inner.txt\", O_CREAT) + write");
close(fd);
c.check(stat("/tmp/ptdir/inner.txt", &mut st) == 0 && st.size == 5, "...it's in that directory");
c.check(openat(AT_FDCWD, "/tmp/ptdir/inner.txt", O_RDONLY) >= 0, "openat(AT_FDCWD, absolute)");
c.check(openat(99, "inner.txt", O_RDONLY) == -EBADF, "openat with a bad dirfd: EBADF");
c.check(openat(99, "/tmp/ptdir/inner.txt", O_RDONLY) >= 0, "...unless the path is absolute");
c.check(syscall5(SYS_MKDIRAT, dfd as u64, c"sub".as_ptr() as u64, 0o755, 0, 0) == 0, "mkdirat(dirfd, \"sub\")");
c.check(sys(SYS_FACCESSAT, dfd as u64, c"sub".as_ptr() as u64, 0) == 0, "faccessat(dirfd, \"sub\")");
c.check(syscall5(SYS_UNLINKAT, dfd as u64, c"sub".as_ptr() as u64, 0, 0, 0) == -EISDIR, "unlinkat on a directory without AT_REMOVEDIR: EISDIR");
c.check(syscall5(SYS_UNLINKAT, dfd as u64, c"sub".as_ptr() as u64, AT_REMOVEDIR, 0, 0) == 0, "unlinkat(AT_REMOVEDIR)");
let r = syscall5(SYS_RENAMEAT, dfd as u64, c"inner.txt".as_ptr() as u64, AT_FDCWD as u64, c"/tmp/renamed.txt".as_ptr() as u64, 0);
c.check(r == 0 && access("/tmp/ptdir/inner.txt") == -ENOENT, "renameat(dirfd, \"inner.txt\", AT_FDCWD, \"/tmp/renamed.txt\")");
c.check(sys(SYS_FCHDIR, dfd as u64, 0, 0) == 0 && stat("../renamed.txt", &mut st) == 0 && st.size == 5, "fchdir(dirfd), then a relative path");
chdir("/");
close(dfd);
let fd = open("/tmp/pt-new", O_WRONLY | O_CREAT);
write(fd, b"new contents");
close(fd);
c.check(rename("/tmp/pt-new", "/tmp/renamed.txt") == 0, "rename a file over another");
c.check(stat("/tmp/renamed.txt", &mut st) == 0 && st.size == 12 && access("/tmp/pt-new") == -ENOENT, "...it replaced it");
c.check(rename("/tmp/renamed.txt", "/mnt/disk0/x.txt") == -EXDEV, "rename across file systems: EXDEV");
c.check(rename("/tmp/ptdir", "/tmp/ptdir2") == 0 && stat("/tmp/ptdir2", &mut st) == 0 && st.is_dir(), "rename an empty directory");
c.check(rename("/tmp/ptdir2", "/tmp/ptdir2/in") == -EINVAL, "rename a directory into itself: EINVAL");
c.check(rename("/tmp/nope", "/tmp/x") == -ENOENT, "rename a missing file: ENOENT");
unlink("/tmp/renamed.txt");
unlink("/tmp/pt-excl");
rmdir("/tmp/ptdir2");
c.check(sys(SYS_GETUID, 0, 0, 0) == 0, "getuid: 0 (root)");
c.done()
}
|