File: initial_dynamic_load.md

package info (click to toggle)
chromium-browser 57.0.2987.98-1~deb8u1
  • links: PTS, VCS
  • area: main
  • in suites: jessie
  • size: 2,637,852 kB
  • ctags: 2,544,394
  • sloc: cpp: 12,815,961; ansic: 3,676,222; python: 1,147,112; asm: 526,608; java: 523,212; xml: 286,794; perl: 92,654; sh: 86,408; objc: 73,271; makefile: 27,698; cs: 18,487; yacc: 13,031; tcl: 12,957; pascal: 4,875; ml: 4,716; lex: 3,904; sql: 3,862; ruby: 1,982; lisp: 1,508; php: 1,368; exp: 404; awk: 325; csh: 117; jsp: 39; sed: 37
file content (229 lines) | stat: -rw-r--r-- 11,480 bytes parent folder | download
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
# Loading the dynamic linker and executable

_Draft_

## Overview

This document discusses how address space should be laid out when a dynamic
linker and an executable are loaded, and how this can be orchestrated with
sel\_ldr and its associated in-browser interface.

## Background

On a typical ELF system such as Linux, the kernel is normally responsible for
loading both the executable and the dynamic linker. The executable is invoked by
filename with execve(). The kernel loads the executable into the process, and
looks for a PT\_INTERP entry in its ELF Program Headers; this specifies the
filename of the dynamic linker (/lib/ld-linux.so.2 for glibc on Linux). If there
is a PT\_INTERP entry, the kernel loads this file too.

Either of these two ELF objects can be relocatable (ET\_DYN) or require loading
at a fixed position in address space (ET\_EXEC). Most often, the dynamic linker
is relocatable and the executable is fixed-position with a standard base address
(0x08048000 on i386). Sometimes the executable is relocatable too (these are
known as PIEs - position-independent executables). For relocatable objects, the
kernel chooses the load address.

There is another way to load the two ELF objects: the dynamic linker can be
invoked directly with execve(). If passed the filename of an executable, the
dynamic linker will load the executable itself.

There are two ways in which we might wish to do this differently in Native
Client's `sel_ldr`.

### Finding the dynamic linker

The Linux kernel looks up PT\_INTERP in a file namespace. In NaCl, however,
there is no built-in filesystem so it is not appropriate for `sel_ldr` to
interpret the PT\_INTERP filename (see AcquiringDynamicLibraries). The second
method -- invoking the dynamic linker directly -- is more appropriate to NaCl.

### Address space allocation

The Linux kernel makes address space allocation decisions: * Allocation
decisions have varied between versions of Linux. Sometimes libc.so and ld.so are
placed below the executable, sometimes above. Additionally, recent Linux
versions perform address space randomisation. * The heap (`brk()`-allocated
memory) goes after whichever object was invoked with execve(). Normally it does
not matter whether this is ld.so or libc.so. (The one exception I have
encountered is where invoking an old version of Emacs through ld.so failed,
because it caused the heap to be placed at a top-bit-set address, and Emacs
wanted to use the top address bit for GC purposes.)

In Native Client, for portability and testability reasons, ideally we do not
want address space allocation decisions to change between versions of NaCl. We
want behaviour to be as deterministic as possible.

Furthermore, address space is likely to be more constrained under NaCl, both
quantitatively (a 1GB limit) and qualitatively (a code/data split -- see
DynamicLoadingOptions).

For these reasons, it may be better to leave load address choices to untrusted
code.

## Possible layouts

*   Load ld.so at 0x20000 (the bottom of available address space).
    *   This is simple and involves only minimal changes to `sel_ldr`. However,
        it does not take advantage of the relocatability of ld.so.
    *   We could pick a larger standard address at which to load the executable,
        e.g. 0x01000000 (16MB). This places limits on how big ld.so and the
        executable can grow, though not severe limits.
    *   Alternatively, we could attempt to place the executable at the top of
        the code region, so that executables grow downwards from 256MB. Hence we
        have a standard executable end address rather than a standard start
        address. This would require linker changes. It involves fixing knowledge
        of the code region size in executables.
*   Load the executable at 0x20000 and either:
    *   load ld.so immediately above, or
    *   load ld.so at the top of the code region.

### Heap placement

If we adopt the Big Segment Gap scheme, address space looks like this: *
0-256MB: ELF code segments can mapped here * 256-512MB: ELF data segments are
mapped here It may be desirable to place the heap at 512MB so that its expansion
does not limit, and is not limited by, mapping of libraries.

If ld.so is loaded at a high address, we can arrange for the heap to start just
before 512MB, at the end of ld.so's data segment's BSS, reusing an otherwise
wasted page and saving upto 4k or 64k.

## Interface 1: sel\_ldr loads both

We could change sel\_ldr to load two ELF objects instead of one, in order to
load both the executable and dynamic linker. The interface for starting a NaCl
process could take two file descriptors.

This involves adding extra complexity to the trusted codebase. Later options
show that this is not necessary.

## Interface 2: sel\_ldr loads ld.so

sel\_ldr can load ld.so, which in turns loads the executable. This requires
little or no change to sel\_ldr.

Suppose we load ld.so at the bottom of address space, at 0x20000. This is where
statically linked, ET\_EXEC executables are loaded at the moment; sel\_ldr
currently only supports loading such executables. There are two ways to
implement loading ld.so at this address: * Change sel\_ldr to support loading
ET\_DYN executables (such as ld.so), but load them with the fixed address of
0x20000. This is a small change. * Link ld.so as ET\_DYN but rewrite its ELF
headers in a post-link step to be ET\_EXEC with a load address of 0x20000.

Alternatively, we could load ld.so at a higher address. sel\_ldr could take an
extra parameter to specify the ELF object's load address, or it could default to
loading the object at the highest possible address in the code region.

### ld.so's PHDRs

ld.so normally contains ELF Program Headers that are currently rejected by
sel\_ldr, in particular `PT_DYNAMIC`, `PT_GNU_EH_FRAME` and `PT_GNU_RELRO`.
(`objdump -x /lib/ld-linux.so.2` also lists `PT_GNU_STACK`, but this is not
relevant to NaCl because the stack is never executable.) Again there are two
ways to deal with these, depending on how much we want to change sel\_ldr: *
Change sel\_ldr to ignore unrecognised Program Headers. This has no security
implications. It is what most ELF loaders do. Most ELF executable loaders look
only at `PT_LOAD` headers. * Change ld.so so that it does not contain the
headers that sel\_ldr does not like. As before, this can be done as a post-link
step, so that we do not have to modify the linker to omit `PT_DYNAMIC` etc. when
linking with `-shared`.

These headers have two uses: * ld.so can read them during initialisation.
Currently, only the non-essential `PT_GNU_RELRO` is used this way. ld.so locates
the `.dynamic` section statically via the symbol `_DYNAMIC` rather than by
searching for `PT_DYNAMIC`. * They can be returned by libc's public interface
`dl_iterate_phdr()`, which is useful for garbage collectors, debuggers, etc.

The ELF Program Headers are normally included at the start of the ELF object's
text segment. We cannot do this under NaCl, because this data will not validate
as code. But we also cannot move the Program Headers to the data segment
because, while in principle they can live at any offset in the ELF file, it is
only at the start of the file that file offsets are the same as in-memory
offsets. glibc's ld.so is taking a shortcut by assuming that it can find its own
Program Headers in memory at runtime using `e_phoff` from its own ELF header.

If we want to support a `dl_iterate_phdr()` that lists ld.so (which could be
desirable for debugging tools), we will probably need to copy ld.so's Program
Headers into ld.so's data segment as a post-link step. This is a small point and
may not be important.

## Interface 3: move ELF parsing out of sel\_ldr

Instead of passing an ELF executable to sel\_ldr, the invoker can pass a list of
mapping instructions. Each instruction specifies a code or data segment to
map/copy into memory. The primary difference from ELF loading is that each
segment can come from a different file.

This arrangement means that ELF parsing can be moved out of the trusted
codebase. ELF parsing could be done by untrusted NaCl code or Javascript code.
Doing this in Javascript would avoid bootstrapping problems. Using Javascript
for this should not be a performance problem because, even though Javascript's
string manipulation facilities are limited, the task is simple and ELF Program
Headers comprise only a small amount of data.

This scheme means we do not need to worry about whether sel\_ldr supports
ET\_DYN executables or whether it rejects Program Header entries that it does
not recognise.

The main advantage of this scheme is not so much the reduction in trusted code
(instead of parsing ELF, we parse a different format), but the increase in
flexibility. It reduces the need to bootstrap a process from inside the process.

"sel\_ldr" would no longer be an accurate name because it would no longer be an
ELF loader!

## Passing arguments from the web browser

If the dynamic linker is responsible for loading the executable, it requires a
mechanism for receiving either the filename of the executable (which it can pass
to open(), which works as described in AcquiringDynamicLibraries) or a file
descriptor for the executable.

The traditional way to pass a filename is via a Unix-style argv list of strings
(as used in `main()`/`execve()`). This is such a widely used interface that it
would make sense to support it in NaCl. Currently `sel_ldr` supports passing
argv to the NaCl process, but this is not exposed in the browser interface. We
have a number of options for supporting argv from the browser:

*   `launch_with_argv()`: Provide a Javascript method for launching a NaCl
    process that takes an argv list-of-strings parameter (as in [the prototype
    implementation](AcquiringDynamicLibraries#Prototype_implementation.md)).
*   Generic IMC messages: On startup, the NaCl process does `imc_accept()` +
    `imc_recvmsg()` to receive a message containing argv, which Javascript code
    must send.
*   Generic startup message: Allow Javascript code to provide a blob of data to
    copy into the NaCl process on startup. This dovetails with interface 3 above
    in which Javascript code specifies segment layout in detail.
    *   This could be a blob with which to initialise the stack. argv and envp
        are stored at the top of the stack on ELF systems. This data structure
        contains pointers, so whoever provides the blob needs to know what
        address it will be loaded at.
    *   The blob could contain argv and envp in some other format.

`launch_with_argv()` has the disadvantage that it introduces a new type of
message that is only used in the special case of process startup.

If we use generic messages to supply argv, we might want to remove the existing
argv mechanism from `sel_ldr` so that NaCl does not have two competing
mechanisms for argv.

## Initial load using existing interfaces

Here is how the initial load can be done using existing browser interfaces where
possible:

HTML: `<embed src="path/to/ld.so.2" type="application/x-nacl-srpc"/>
`

Javascript: `nacl_elt.startup_argv(["arg0", "path/to/executable", "arg1",
"arg2"])
`

ld.so code in NaCl process: `fd = imc_accept(initial_fd); message =
imc_recvmsg(fd); // decode argv from message // continue rest of startup using
argv
`

*   ld.so is ET\_EXEC with a start address of 0x20000.
*   executable is ET\_EXEC with a start address of 0x1000000.