import re
import subprocess
MAX_ALLOWED_GLIBC_VERSION = [2, 26]
MAX_ALLOWED_GLIBC_VERSION_ARCH = {
"riscv64": [2, 33],
}
VERSION_PATTERN = re.compile("GLIBC_([0-9\.]+)")
SECTION_PATTERN = re.compile(r"^ *\[ *[0-9]+\] +(\S+) +\S+ + ([0-9a-f]+) .*$")
SYMBOL_ALLOWLIST = {
"fts64_close",
"fts64_open",
"fts64_read",
"memfd_create",
}
def reversion_glibc(bin_file: str, arch: str) -> None:
max_allowed_glibc_version = MAX_ALLOWED_GLIBC_VERSION_ARCH.get(
arch, MAX_ALLOWED_GLIBC_VERSION)
default_version = {}
supported_version = {}
stdout = subprocess.check_output(
["readelf", "--dyn-syms", "--wide", bin_file])
for line in stdout.decode("utf-8").split("\n"):
cols = re.split("\s+", line)
if len(cols) > 7 and cols[7] == "[<localentry>:":
cols.pop(7)
cols.pop(7)
if len(cols) < 9:
continue
index = cols[1].rstrip(":")
if not index.isdigit():
continue
index = int(index)
name = cols[8].split("@")
if len(name) < 2:
continue
base_name = name[0]
version = name[-1]
is_default = len(name) > 2
if version.startswith("XCRYPT_"):
version = [10**10]
else:
match = re.match(VERSION_PATTERN, version)
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)
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:
raise Exception("No .gnu.version section found")
bin_data = bytearray(open(bin_file, "rb").read())
for name, (version, index) in default_version.items():
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
old_default = gnu_version_addr + 2 * index + 1
assert (bin_data[old_default] & 0x80) == 0
bin_data[old_default] ^= 0x80
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)