/
ldcache.cpp
263 lines (224 loc) · 8.17 KB
/
ldcache.cpp
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
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <vector>
#include <stout/os.hpp>
#include <stout/try.hpp>
#include "linux/ldcache.hpp"
using std::string;
using std::vector;
// There are three formats for ld.so.cache. The "old" pre-glibc
// format 2.2 listed the number of library entries, followed by
// the entries themselves, followed by a string table holding
// strings pointed to by the library entries. This format is
// summarized below:
//
// HEADER_MAGIC_OLD
// nlibs
// libs[0]
// ...
// libs[nlibs-1]
// first string\0second string\0...last string\0
// ^ ^
// start of string table end of string table
//
// For glibc 2.2 and beyond, a "new" format was created so that each
// library entry could hold more meta-data about the libraries they
// reference. To preserve backwards compatibility, a "compat" format
// was introduced where the new format was embedded in the old
// format inside its string table (simply moving all existing strings
// further down in the string table). This makes sense for backwards
// compatibility because code that could parse the old format still
// works (the offsets for strings pointed to by the library entries
// are just larger now).
//
// However, it adds complications when parsing for the compat format
// because the new format header needs to be aligned on an 8 byte
// boundary (potentially pushing the start address of the string table
// down a few bytes). A summary of the compat format with the new
// format embedded in the old format with annotations on the start
// address of the string table can be seen below:
//
// HEADER_MAGIC_OLD
// nlibs
// libs[0]
// ...
// libs[nlibs-1]
// pad (align for new format)
// HEADER_MAGIC_NEW <-- start of string table
// nlibs
// len_strings
// unused // 20 bytes reserved for extensions
// libs[0]
// ...
// libs[nlibs-1]
// first string\0second string\0...last string\0
// ^
// end of string table
//
// Then from glibc 2.32 the default changed to the new format, without
// the compatibility header:
//
// HEADER_MAGIC_NEW
// nlibs
// len_strings
// unused // 20 bytes reserved for extensions
// libs[0]
// ...
// libs[nlibs-1]
// first string\0second string\0...last string\0
// ^
// end of string table
//
// We support the compat and new formats: no need to support the old
// format since glibc 2.2 was released in late 2000.
namespace ldcache {
constexpr char HEADER_MAGIC_OLD[] = "ld.so-1.7.0";
constexpr char HEADER_MAGIC_NEW[] = "glibc-ld.so.cache1.1";
#define IS_ELF 0x00000001
struct HeaderOld
{
char magic[sizeof(HEADER_MAGIC_OLD) - 1];
uint32_t libraryCount; // Number of library entries.
};
struct EntryOld
{
int32_t flags; // 0x01 indicates ELF library.
uint32_t key; // String table index.
uint32_t value; // String table index.
};
struct HeaderNew
{
char magic[sizeof(HEADER_MAGIC_NEW) - 1];
uint32_t libraryCount; // Number of library entries.
uint32_t stringsLength; // Length of "actual" string table.
uint32_t unused[5]; // Leave space for future extensions
// and align to 8 byte boundary.
};
struct EntryNew
{
int32_t flags; // Flags bits determine arch and library type.
uint32_t key; // String table index.
uint32_t value; // String table index.
uint32_t osVersion; // Required OS version.
uint64_t hwcap; // Hwcap entry.
};
// Returns a 'boundary' aligned pointer by rounding up to
// the nearest multiple of 'boundary'.
static inline const char* align(const char* address, size_t boundary)
{
if ((size_t)address % boundary == 0) {
return address;
}
return (address + boundary) - ((size_t)address % boundary);
}
Try<vector<Entry>> parse(const string& path)
{
// Read the complete file into a buffer
Try<string> buffer = os::read(path);
if (buffer.isError()) {
return Error(buffer.error());
}
const char* data = buffer->data();
HeaderNew* headerNew = (HeaderNew*)data;
if (data + sizeof(HeaderNew) >= buffer->data() + buffer->size()) {
return Error("Invalid format");
}
if (strncmp(headerNew->magic,
HEADER_MAGIC_NEW,
sizeof(HEADER_MAGIC_NEW) - 1) != 0) {
// If the data doesn't start with the new header, it must be a
// compat format, therefore we expect an old header.
HeaderOld* headerOld = (HeaderOld*)data;
data += sizeof(HeaderOld);
if (data >= buffer->data() + buffer->size()) {
return Error("Invalid format");
}
// Validate our header magic.
if (strncmp(headerOld->magic,
HEADER_MAGIC_OLD,
sizeof(HEADER_MAGIC_OLD) - 1) != 0) {
return Error("Invalid format");
}
data += headerOld->libraryCount * sizeof(EntryOld);
if (data >= buffer->data() + buffer->size()) {
return Error("Invalid format");
}
// The new format header and all of its library entries are embedded
// in the old format's string table (the current location of data).
// However, the header is aligned on an 8 byte boundary, so we
// need to align 'data' to get it to point to the new header.
data = align(data, alignof(HeaderNew));
if (data >= buffer->data() + buffer->size()) {
return Error("Invalid format");
}
headerNew = (HeaderNew*)data;
}
// Construct pointers to all of the important regions in the new
// format: the header, the libentry array, and the new string table
// (which starts at the same address as the aligned headerNew pointer).
data += sizeof(HeaderNew);
if (data >= buffer->data() + buffer->size()) {
return Error("Invalid format");
}
EntryNew* entriesNew = (EntryNew*)data;
data += headerNew->libraryCount * sizeof(EntryNew);
if (data >= buffer->data() + buffer->size()) {
return Error("Invalid format");
}
// The start of the strings table is at the beginning of
// the new header, per the above format description.
char* strings = (char*)headerNew;
// Adjust the pointer to add on the additional size of the strings
// contained in the string table. At this point, 'data' should
// point to an address just beyond the end of the file.
data += headerNew->stringsLength;
if ((size_t)(data - buffer->data()) != buffer->size()) {
return Error("Invalid format");
}
// Make sure the very last character in the buffer is a '\0'.
// This way, no matter what strings we index in the string
// table, we know they will never run beyond the end of the
// file buffer when extracting them.
if (*(data - 1) != '\0') {
return Error("Invalid format");
}
// Build our vector of ldcache entries.
vector<Entry> ldcache;
for (uint32_t i = 0; i < headerNew->libraryCount; i++) {
if (!(entriesNew[i].flags & IS_ELF)) {
continue;
}
if (strings + entriesNew[i].key >= data) {
return Error("Invalid format");
}
if (strings + entriesNew[i].value >= data) {
return Error("Invalid format");
}
Entry entry;
entry.name = &strings[entriesNew[i].key];
entry.path = &strings[entriesNew[i].value];
ldcache.push_back(entry);
}
return ldcache;
}
} // namespace ldcache {