compiler/tests/fixtures/runtime/linux-vdso/main.ldn
1-- The vDSO the auxiliary vector names: platform/linux finds its
2-- `clock_gettime` by name and version, which reads the same clocks the
3-- system call does, and a copy of the image, damaged one byte at a time
4-- or cut short anywhere, finds nothing or something inside it and never
5-- traps, so a malformed vDSO falls back to the system call. Without its
6-- version tables the name alone decides, and without DT_HASH the GNU
7-- hash table counts the symbols. Each check that fails returns its own
8-- code.
9import platform/linux
10import platform/linux/vdso
11
12-- Room for a copy of the image: the kernel's is two pages or so.
13mut copy: [65536]u8
14
15-- The image's length in the copy.
16mut copied: usize
17
18-- The clock `clock` read through the system call, in nanoseconds.
19by_system_call: (clock: i32) -> (at: i64) =
20 mut record: linux.timespec = zeroed
21 at = -1
22 return when linux.clock_gettime(clock, addr record) <> 0
23 at = record.seconds * 1000000000 + record.nanoseconds
24end by_system_call
25
26-- The clock `clock` read through the vDSO function at ADDRESS.
27by_vdso: (address: usize, clock: i32) -> (at: i64) =
28 function: vdso.clock_gettime = vdso.clock_gettime(address)
29 mut record: linux.timespec = zeroed
30 at = -1
31 return when function(clock, addr record) <> 0
32 at = record.seconds * 1000000000 + record.nanoseconds
33end by_vdso
34
35-- Readings through the vDSO fall between system calls made before and
36-- after them, on the monotonic clock, and the realtime clocks agree
37-- within a second.
38agrees: (address: usize) -> (yes: bool) =
39 yes = false
40 for round in usize(0)..<100 do
41 _ = round
42 before: i64 = by_system_call(linux.clock_monotonic)
43 between: i64 = by_vdso(address, linux.clock_monotonic)
44 after: i64 = by_system_call(linux.clock_monotonic)
45 return when before < 0 or between < before or after < between
46 end for
47 wall: i64 = by_vdso(address, linux.clock_realtime)
48 kernel: i64 = by_system_call(linux.clock_realtime)
49 return when wall < 0 or kernel < wall or kernel - wall > 1000000000
50 yes = true
51end agrees
52
53-- Whether a lookup in the first LENGTH bytes of the copy returns without
54-- trapping and, when it finds the clock, finds it inside them.
55holds: (length: usize) -> (yes: bool) =
56 offset: usize = found_at(copy[0..<length], vdso.clock_symbol,
57 vdso.clock_version)
58 yes = offset == no_offset or offset < length
59end holds
60
61-- Every byte of the copy set to zero, to every bit, and to itself with
62-- its top bit flipped, one at a time and put back after.
63damaged: () -> (yes: bool) =
64 yes = false
65 for at in usize(0)..<copied do
66 kept: u8 = copy[at]
67 copy[at] = 0
68 return when not holds(copied)
69 copy[at] = 255
70 return when not holds(copied)
71 copy[at] = kept ^ 128
72 return when not holds(copied)
73 copy[at] = kept
74 end for
75 yes = true
76end damaged
77
78-- The copy cut short at every length.
79truncated: () -> (yes: bool) =
80 yes = false
81 for length in usize(0)..<copied do
82 return when not holds(length)
83 end for
84 yes = true
85end truncated
86
87-- The little-endian u64 at AT in the copy, and writing one.
88read_double: (at: usize) -> (value: u64) =
89 value = 0
90 for index in usize(0)..<8 do
91 value = value | (u64(copy[at + index]) << u64(8 * index))
92 end for
93end read_double
94
95write_double: (at: usize, value: u64) -> none =
96 for index in usize(0)..<8 do
97 copy[at + index] = u8((value >> u64(8 * index)) & 255)
98 end for
99end write_double
100
101-- Where in the copy the dynamic entry tagged TAG lies, or zero.
102entry_of: (tag: u64) -> (at: usize) =
103 at = 0
104 headers: usize = usize(read_double(32))
105 count: usize = usize(copy[56]) | (usize(copy[57]) << 8)
106 for index in usize(0)..<count do
107 header: usize = headers + index * 56
108 if copy[header] == 2 and copy[header + 1] == 0 then
109 dynamic: usize = usize(read_double(header + 8))
110 mut entry: usize = dynamic
111 loop do
112 found: u64 = read_double(entry)
113 return when found == 0
114 if found == tag then
115 at = entry
116 return
117 end if
118 entry += 16
119 end loop
120 end if
121 end for
122end entry_of
123
124-- A tag no dynamic loader reads, which hides the entry it replaces.
125hidden: u64 = 0x6fffff00
126
127-- Where in BYTES the function NAME of VERSION is, or no_offset.
128no_offset: usize = 0 -% 1
129
130found_at: (bytes: []u8, name: []u8, version: []u8) -> (offset: usize) =
131 (found, offset: at) := vdso.find(bytes, name, version)
132 offset = if found then at else no_offset end if
133end found_at
134
135-- Where the auxiliary vector says the vDSO is.
136vdso_base: () -> (base: usize) =
137 (found: _, value) := linux.auxiliary(vdso.at_sysinfo_ehdr)
138 base = usize(value)
139end vdso_base
140
141-- The vDSO's function NAME of VERSION's address, or zero.
142address_of: (name: []u8, version: []u8) -> (address: usize) =
143 (found, address: at) := vdso.symbol(name, version)
144 address = if found then at else 0 end if
145end address_of
146
147public main: () -> (code: i32) =
148 (found: present, image) := vdso.kernel_image()
149 code = 1
150 return when not present or lenof image < 64 or lenof image > 65536
151 code = 2
152 name: []u8 = vdso.clock_symbol
153 version: []u8 = vdso.clock_version
154 offset: usize = found_at(image, name, version)
155 return when offset >= lenof image
156 address: usize = address_of(name, version)
157 return when address <> vdso_base() + offset
158 code = 3
159 return when not agrees(address)
160 code = 4
161 return when found_at(image, name, "LINUX_0.1") <> no_offset
162 or found_at(image, "__vdso_no_such_function", version) <> no_offset
163 code = 5
164 copied = lenof image
165 for at in usize(0)..<copied do
166 copy[at] = image[at]
167 end for
168 return when not damaged()
169 code = 6
170 return when not truncated()
171 code = 7
172 -- Without DT_HASH the GNU hash table counts the symbols, and the same
173 -- function is found where it was.
174 hash: usize = entry_of(4)
175 if hash <> 0 and entry_of(0x6ffffef5) <> 0 then
176 write_double(hash, hidden)
177 return when found_at(copy[0..<copied], name, version) <> offset
178 write_double(hash, 4)
179 end if
180 code = 8
181 -- Without version definitions, the name alone decides.
182 definitions: usize = entry_of(0x6ffffffc)
183 return when definitions == 0
184 write_double(definitions, hidden)
185 return when found_at(copy[0..<copied], name, "LINUX_0.1") <> offset
186 code = 42
187end main