blob: e93334465911d3ec37f38d51249486d5d317bdb3 [file] [log] [blame]
// 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 two formats for ld.so.cache. The 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, 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 new 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 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
//
// We currently only support the new 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();
// Grab a pointer to the old format header (for verification of
// HEADER_MAGIC_OLD later on). Then jump forward to the location of
// the new format header (it is the only format we support).
HeaderOld* headerOld = (HeaderOld*)data;
data += sizeof(HeaderOld);
if (data >= buffer->data() + buffer->size()) {
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");
}
// 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).
HeaderNew* headerNew = (HeaderNew*)data;
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");
}
// Validate our header magic.
if (strncmp(headerOld->magic,
HEADER_MAGIC_OLD,
sizeof(HEADER_MAGIC_OLD) - 1) != 0) {
return Error("Invalid format");
}
if (strncmp(headerNew->magic,
HEADER_MAGIC_NEW,
sizeof(HEADER_MAGIC_NEW) - 1) != 0) {
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 {