/
reversion_glibc.py
executable file
·124 lines (105 loc) · 3.99 KB
/
reversion_glibc.py
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
#!/usr/bin/env python3
# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Rewrite incompatible default symbols in glibc.
"""
import re
import subprocess
import sys
# This constant comes from the oldest glibc version in
# //chrome/installer/linux/debian/dist_package_versions.json and
# //chrome/installer/linux/rpm/dist_package_provides.json
MAX_ALLOWED_GLIBC_VERSION = [2, 26]
VERSION_PATTERN = re.compile('GLIBC_([0-9\.]+)')
SECTION_PATTERN = re.compile(r'^ *\[ *[0-9]+\] +(\S+) +\S+ + ([0-9a-f]+) .*$')
# Some otherwise disallowed symbols are referenced in the linux-chromeos build.
# To continue supporting it, allow these symbols to remain enabled.
SYMBOL_ALLOWLIST = {
'fts64_close',
'fts64_open',
'fts64_read',
'memfd_create',
}
# The two dictionaries below map from symbol name to
# (symbol version, symbol index).
#
# The default version for a given symbol (which may be unsupported).
default_version = {}
# The max supported symbol version for a given symbol.
supported_version = {}
# The file name of the binary we're going to rewrite.
BIN_FILE = sys.argv[1]
# Populate |default_version| and |supported_version| with data from readelf.
stdout = subprocess.check_output(['readelf', '--dyn-syms', '--wide', BIN_FILE])
for line in stdout.decode("utf-8").split('\n'):
cols = re.split('\s+', line)
# Skip the preamble.
if len(cols) < 9:
continue
index = cols[1].rstrip(':')
# Skip the header.
if not index.isdigit():
continue
index = int(index)
name = cols[8].split('@')
# Ignore unversioned symbols.
if len(name) < 2:
continue
base_name = name[0]
version = name[-1]
# The default version will have '@@' in the name.
is_default = len(name) > 2
if version.startswith('XCRYPT_'):
# Prefer GLIBC_* versioned symbols over XCRYPT_* ones. Set the version to
# something > MAX_ALLOWED_GLIBC_VERSION so this symbol will not be picked.
version = [float('inf')]
else:
match = re.match(VERSION_PATTERN, version)
# Ignore symbols versioned with GLIBC_PRIVATE.
if not match:
continue
version = [int(part) for part in match.group(1).split('.')]
if version < MAX_ALLOWED_GLIBC_VERSION:
old_supported_version = supported_version.get(base_name, ([-1], -1))
supported_version[base_name] = max((version, index), old_supported_version)
if is_default:
default_version[base_name] = (version, index)
# Get the offset into the binary of the .gnu.version section from readelf.
stdout = subprocess.check_output(['readelf', '--sections', '--wide', BIN_FILE])
for line in stdout.decode("utf-8").split('\n'):
if match := SECTION_PATTERN.match(line):
section_name, address = match.groups()
if section_name == '.gnu.version':
gnu_version_addr = int(address, base=16)
break
else:
print('No .gnu.version section found', file=sys.stderr)
sys.exit(1)
# Rewrite the binary.
bin_data = bytearray(open(BIN_FILE, 'rb').read())
for name, (version, index) in default_version.items():
# No need to rewrite the default if it's already an allowed version.
if version <= MAX_ALLOWED_GLIBC_VERSION:
continue
if name in SYMBOL_ALLOWLIST:
continue
elif name in supported_version:
_, supported_index = supported_version[name]
else:
supported_index = -1
# The .gnu.version section is divided into 16-bit chunks that give the
# symbol versions. The 16th bit is a flag that's false for the default
# version. The data is stored in little-endian so we need to add 1 to
# get the address of the byte we want to flip.
#
# Disable the unsupported symbol.
old_default = gnu_version_addr + 2 * index + 1
assert (bin_data[old_default] & 0x80) == 0
bin_data[old_default] ^= 0x80
# If we found a supported version, enable that as default.
if supported_index != -1:
new_default = gnu_version_addr + 2 * supported_index + 1
assert (bin_data[new_default] & 0x80) == 0x80
bin_data[new_default] ^= 0x80
open(BIN_FILE, 'wb').write(bin_data)