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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
|
typedef unsigned long u64;
typedef long i64;
static i64 sys6(i64 n, i64 a, i64 b, i64 c, i64 d, i64 e, i64 f) {
register i64 r10 __asm__("r10") = d;
register i64 r8 __asm__("r8") = e;
register i64 r9 __asm__("r9") = f;
i64 ret;
__asm__ volatile("syscall"
: "=a"(ret)
: "a"(n), "D"(a), "S"(b), "d"(c), "r"(r10), "r"(r8), "r"(r9)
: "rcx", "r11", "memory");
return ret;
}
#define sys0(n) sys6(n, 0, 0, 0, 0, 0, 0)
#define sys1(n, a) sys6(n, (i64)(a), 0, 0, 0, 0, 0)
#define sys2(n, a, b) sys6(n, (i64)(a), (i64)(b), 0, 0, 0, 0)
#define sys3(n, a, b, c) sys6(n, (i64)(a), (i64)(b), (i64)(c), 0, 0, 0)
#define sys4(n, a, b, c, d) sys6(n, (i64)(a), (i64)(b), (i64)(c), (i64)(d), 0, 0)
enum {
NR_read = 0, NR_write = 1, NR_close = 3, NR_stat = 4, NR_fstat = 5,
NR_lseek = 8, NR_mmap = 9, NR_munmap = 11, NR_brk = 12,
NR_rt_sigaction = 13, NR_rt_sigprocmask = 14, NR_ioctl = 16,
NR_writev = 20, NR_access = 21, NR_getpid = 39, NR_fork = 57,
NR_exit = 60, NR_wait4 = 61, NR_kill = 62, NR_uname = 63,
NR_getcwd = 79, NR_chdir = 80, NR_rename = 82, NR_mkdir = 83,
NR_rmdir = 84, NR_unlink = 87, NR_getuid = 102, NR_arch_prctl = 158,
NR_getdents64 = 217, NR_set_tid_address = 218, NR_clock_gettime = 228,
NR_clock_nanosleep = 230, NR_exit_group = 231, NR_openat = 257,
NR_newfstatat = 262, NR_unlinkat = 263,
};
#define AT_FDCWD (-100)
#define AT_REMOVEDIR 0x200
#define O_RDONLY 0
#define O_WRONLY 1
#define O_CREAT 0100
#define O_TRUNC 01000
#define O_DIRECTORY 0200000
#define ENOENT 2
#define EFAULT 14
#define SIGUSR1 10
#define SA_RESTORER 0x04000000
#define ARCH_SET_FS 0x1002
#define S_IFMT 0170000
#define S_IFREG 0100000
#define S_IFDIR 0040000
struct kstat {
u64 dev, ino, nlink;
unsigned mode, uid, gid, pad0;
u64 rdev;
i64 size, blksize, blocks;
i64 atime, atime_ns, mtime, mtime_ns, ctime, ctime_ns;
i64 unused[3];
};
struct timespec { i64 sec, nsec; };
struct iovec { const void *base; u64 len; };
struct utsname { char f[6][65]; };
struct kaction { void (*handler)(int); u64 flags; void (*restorer)(void); u64 mask; };
void *memset(void *d, int c, u64 n) { char *p = d; while (n--) *p++ = (char)c; return d; }
void *memcpy(void *d, const void *s, u64 n) { char *p = d; const char *q = s; while (n--) *p++ = *q++; return d; }
static u64 slen(const char *s) { u64 n = 0; while (s[n]) n++; return n; }
static void puts_(const char *s) { sys3(NR_write, 1, s, slen(s)); }
static void putu(u64 v) {
char b[24]; int i = 23; b[i] = 0;
do { b[--i] = (char)('0' + v % 10); v /= 10; } while (v);
puts_(b + i);
}
static int seq(const char *a, const char *b) { while (*a && *a == *b) a++, b++; return *a == *b; }
static const char *join(char *out, const char *dir, const char *name) {
char *p = out;
while (*dir) *p++ = *dir++;
*p++ = '/';
while (*name) *p++ = *name++;
*p = 0;
return out;
}
static int failures;
static void check(int ok, const char *what) {
puts_(ok ? "rawabi: ok " : "rawabi: FAIL ");
puts_(what);
puts_("\n");
if (!ok) failures++;
}
static double leibniz_pi(int terms) {
volatile double sum = 0.0;
for (int k = 0; k < terms; k++)
sum += (k & 1 ? -4.0 : 4.0) / (2.0 * k + 1.0);
return sum;
}
static double sqrt_sd(double x) { double r; __asm__("sqrtsd %1, %0" : "=x"(r) : "x"(x)); return r; }
static void putf(double v) {
if (v < 0) { puts_("-"); v = -v; }
u64 whole = (u64)v, frac = (u64)((v - (double)whole) * 1e6 + 0.5);
if (frac >= 1000000) { whole++; frac -= 1000000; }
putu(whole); puts_(".");
for (u64 d = 100000; d > frac && d > 1; d /= 10) puts_("0");
putu(frac);
}
static volatile int got_signal;
static volatile double handler_result;
static void on_usr1(int sig) {
handler_result = sqrt_sd(16.0) * sqrt_sd(0.25);
got_signal = sig;
}
__asm__(".text\n.global rawabi_restorer\nrawabi_restorer:\n"
" mov $15, %eax\n syscall\n hlt\n");
__attribute__((visibility("hidden"))) void rawabi_restorer(void);
static u64 tls_block[4];
static int run(int argc, char **argv) {
const char *dir = argc > 1 ? argv[1] : "/tmp";
char work[256], f1[256], f2[256], cwd[256];
puts_("rawabi: a static Linux binary with no libc, pid ");
putu((u64)sys0(NR_getpid));
puts_("\n");
tls_block[0] = (u64)tls_block;
i64 r = sys2(NR_arch_prctl, ARCH_SET_FS, tls_block);
u64 self;
__asm__ volatile("mov %%fs:0, %0" : "=r"(self));
check(r == 0 && self == (u64)tls_block, "arch_prctl(ARCH_SET_FS) + %fs:0");
check(sys1(NR_set_tid_address, &tls_block[1]) == sys0(NR_getpid), "set_tid_address returns the tid");
u64 oldmask = ~0ul;
check(sys4(NR_rt_sigprocmask, 0, 0, &oldmask, 8) == 0 && oldmask == 0, "rt_sigprocmask(.., oldset, 8)");
i64 uid = sys0(NR_getuid);
check(uid >= 0, "getuid");
struct utsname u;
check(sys1(NR_uname, &u) == 0 && seq(u.f[0], "Rustbelt") | seq(u.f[0], "Linux"), "uname");
puts_("rawabi: uname: "); puts_(u.f[0]); puts_(" "); puts_(u.f[2]); puts_("\n");
u64 cur = (u64)sys1(NR_brk, 0);
u64 grown = (u64)sys1(NR_brk, cur + 65536);
if (grown == cur + 65536) ((volatile char *)cur)[65535] = 1;
check(grown == cur + 65536, "brk grows the heap");
char *m = (char *)sys6(NR_mmap, 0, 8192, 3, 0x22, -1, 0);
if ((u64)m < (u64)-4096) m[8191] = 7;
check((u64)m < (u64)-4096 && sys2(NR_munmap, m, 8192) == 0, "mmap/munmap anonymous");
join(work, dir, "rawabi.d");
join(f1, work, "a.txt");
join(f2, work, "b.txt");
sys1(NR_unlink, f1); sys1(NR_unlink, f2); sys1(NR_rmdir, work);
check(sys2(NR_mkdir, work, 0755) == 0, "mkdir");
int fd = (int)sys4(NR_openat, AT_FDCWD, f1, O_WRONLY | O_CREAT | O_TRUNC, 0644);
struct iovec iov[2] = { { "hello ", 6 }, { "from rawabi\n", 12 } };
check(fd >= 0 && sys3(NR_writev, fd, iov, 2) == 18, "openat(AT_FDCWD, O_CREAT) + writev");
sys1(NR_close, fd);
struct kstat st;
check(sys2(NR_stat, f1, &st) == 0 && (st.mode & S_IFMT) == S_IFREG && st.size == 18, "stat: regular file, 18 bytes");
check(sys4(NR_newfstatat, AT_FDCWD, work, &st, 0) == 0 && (st.mode & S_IFMT) == S_IFDIR, "newfstatat: directory");
check(sys2(NR_access, f1, 4) == 0 && sys2(NR_access, f2, 0) == -ENOENT, "access: R_OK / ENOENT");
check(sys2(NR_stat, (char *)0, &st) == -EFAULT, "stat(NULL) = -EFAULT");
check(sys2(NR_rename, f1, f2) == 0 && sys2(NR_stat, f1, &st) == -ENOENT, "rename");
char buf[64];
fd = (int)sys4(NR_openat, AT_FDCWD, f2, O_RDONLY, 0);
i64 n = sys3(NR_read, fd, buf, sizeof buf);
struct kstat fst;
check(n == 18 && buf[0] == 'h' && buf[17] == '\n', "read back the renamed file");
check(sys2(NR_fstat, fd, &fst) == 0 && fst.size == 18 && sys3(NR_lseek, fd, 6, 0) == 6, "fstat + lseek");
sys1(NR_close, fd);
fd = (int)sys4(NR_openat, AT_FDCWD, work, O_RDONLY | O_DIRECTORY, 0);
char dents[512];
int found = 0;
n = sys3(NR_getdents64, fd, dents, sizeof dents);
for (i64 off = 0; off < n;) {
unsigned short reclen = *(unsigned short *)(dents + off + 16);
if (seq(dents + off + 19, "b.txt")) found = 1;
off += reclen;
}
sys1(NR_close, fd);
check(found, "getdents64 lists b.txt");
check(sys1(NR_chdir, work) == 0 && sys2(NR_getcwd, cwd, sizeof cwd) > 0 && seq(cwd, work), "chdir + getcwd");
check(sys3(NR_unlinkat, AT_FDCWD, "b.txt", 0) == 0, "unlinkat(AT_FDCWD, relative path)");
sys1(NR_chdir, dir);
check(sys3(NR_unlinkat, AT_FDCWD, work, AT_REMOVEDIR) == 0, "unlinkat(AT_REMOVEDIR)");
struct timespec t0, t1, nap = { 0, 20 * 1000 * 1000 };
sys2(NR_clock_gettime, 1, &t0);
i64 slept = sys4(NR_clock_nanosleep, 1, 0, &nap, 0);
sys2(NR_clock_gettime, 1, &t1);
i64 ms = (t1.sec - t0.sec) * 1000 + (t1.nsec - t0.nsec) / 1000000;
check(slept == 0 && ms >= 19, "clock_nanosleep(CLOCK_MONOTONIC, 20 ms)");
double pi = leibniz_pi(200000);
double root = sqrt_sd(pi * pi);
puts_("rawabi: pi ~ "); putf(pi); puts_(", sqrt(pi^2) = "); putf(root); puts_("\n");
check(pi > 3.14158 && pi < 3.14160 && root == pi, "SSE double math");
struct kaction sa = { on_usr1, SA_RESTORER, rawabi_restorer, 0 };
check(sys4(NR_rt_sigaction, SIGUSR1, &sa, 0, 8) == 0, "rt_sigaction with SA_RESTORER");
double before = pi * 2.0;
sys2(NR_kill, sys0(NR_getpid), SIGUSR1);
double after = before / 2.0;
check(got_signal == SIGUSR1 && handler_result == 2.0 && after == pi, "xmm state survives a signal handler");
i64 pid = sys0(NR_fork);
if (pid == 0) {
double c = leibniz_pi(1000);
sys1(NR_exit, c > 3.0 ? 7 : 1);
}
int status = 0;
char rusage[144];
check(pid > 0 && sys4(NR_wait4, pid, &status, 0, rusage) == pid && ((status >> 8) & 0xff) == 7,
"fork + wait4(pid, &status, 0, &rusage)");
puts_(failures ? "rawabi: some checks FAILED\n" : "rawabi: all checks passed\n");
return failures;
}
__attribute__((used)) static void start_c(u64 *sp) {
int argc = (int)sp[0];
sys1(NR_exit_group, run(argc, (char **)(sp + 1)));
}
__asm__(".text\n.global _start\n_start:\n"
" xor %ebp, %ebp\n mov %rsp, %rdi\n and $-16, %rsp\n call start_c\n hlt\n");
|