#!/usr/bin/env perl use strict; use warnings; use 5.020; use experimental 'signatures'; no warnings 'experimental::signatures'; # see http://www.lightofdawn.org/wiki/wiki.cgi/NewAppsOnOldGlibc for details # as of currently, only `log2` is being used from GLIBC_2.29 and # `pthread_sigmask` and `pthread_getattr_np` is being used from GLIBC_2.32, and # corresponding versions from GLIBC_2.2.5 do exist, so we can just swap them # out. my $bin = $ARGV[0]; system("readelf -sW $bin | grep -q 'GLIBC_2.\\(29\\|32\\)'"); if ($?) { warn "no symbols from GLIBC_2.29 or GLIBC_2.32 found, skipping\n"; exit(0) } my %readelf_v = parse_readelf_v($bin); my %readelf_s = parse_readelf_s($bin); my $bin_contents = do { local $/; open my $bin_fh, '<', $bin or die "couldn't open $bin: $!"; <$bin_fh> }; # we could just make the GLIBC_2.29 symbol weak (as described in the above # page), but that causes a warning to be printed every time you run the binary, # which is obnoxious # use constant VER_FLG_WEAK => 0x02; # my $prev_glibc_229_flags = substr $bin_contents, $glibc_229_offset + 0x04, 2, pack('s', VER_FLG_WEAK); # if (unpack('s', $prev_glibc_229_flags) != 0) { # die "GLIBC_2.29 did not have flags set to 0\n"; # } # instead, we overwrite vna_hash, vna_flags, vna_other, vna_name with the 2.2.5 # versions (but leave vna_next the same) - this causes there to be multiple # entries describing GLIBC_2.2.5, but this appears to be harmless my $gnu_version_r_offset = $readelf_v{'.gnu.version_r'}{offset}; my $fix_libc = defined($readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.32'}{offset}); my $fix_libm = defined($readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.29'}{offset}); if ($fix_libc) { my $glibc_232_libc_offset = $gnu_version_r_offset + $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.32'}{offset}; my $glibc_225_libc_offset = $gnu_version_r_offset + $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.2.5'}{offset}; substr($bin_contents, $glibc_232_libc_offset + 0x00, 12, substr($bin_contents, $glibc_225_libc_offset + 0x00, 12)); } if ($fix_libm) { my $glibc_229_libm_offset = $gnu_version_r_offset + $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.29'}{offset}; my $glibc_225_libm_offset = $gnu_version_r_offset + $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.2.5'}{offset}; substr($bin_contents, $glibc_229_libm_offset + 0x00, 12, substr($bin_contents, $glibc_225_libm_offset + 0x00, 12)); } my $gnu_version_offset = $readelf_v{'.gnu.version'}{offset}; my $fix_log2 = defined($readelf_s{'log2@GLIBC_2.29'}{ver}) && $readelf_s{'log2@GLIBC_2.29'}{ver} == $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.29'}{ver}; my $fix_pthread_sigmask = defined($readelf_s{'pthread_sigmask@GLIBC_2.32'}{ver}) && $readelf_s{'pthread_sigmask@GLIBC_2.32'}{ver} == $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.32'}{ver}; my $fix_pthread_getattr_np = defined($readelf_s{'pthread_getattr_np@GLIBC_2.32'}{ver}) && $readelf_s{'pthread_getattr_np@GLIBC_2.32'}{ver} == $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.32'}{ver}; if ($fix_log2) { my $log2_offset = $gnu_version_offset + 2 * $readelf_s{'log2@GLIBC_2.29'}{num}; my $prev_log2_version = substr $bin_contents, $log2_offset, 2, pack('s', $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.2.5'}{ver}); if (unpack('s', $prev_log2_version) != $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.29'}{ver}) { die "log2 doesn't appear to use the symbol version from GLIBC_2.29\n"; } } if ($fix_pthread_sigmask) { my $pthread_sigmask_offset = $gnu_version_offset + 2 * $readelf_s{'pthread_sigmask@GLIBC_2.32'}{num}; my $prev_pthread_sigmask_version = substr $bin_contents, $pthread_sigmask_offset, 2, pack('s', $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.2.5'}{ver}); if (unpack('s', $prev_pthread_sigmask_version) != $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.32'}{ver}) { die "pthread_sigmask doesn't appear to use the symbol version from GLIBC_2.32\n"; } } if ($fix_pthread_getattr_np) { my $pthread_getattr_np_offset = $gnu_version_offset + 2 * $readelf_s{'pthread_getattr_np@GLIBC_2.32'}{num}; my $prev_pthread_getattr_np_version = substr $bin_contents, $pthread_getattr_np_offset, 2, pack('s', $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.2.5'}{ver}); if (unpack('s', $prev_pthread_getattr_np_version) != $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.32'}{ver}) { die "pthread_getattr_np doesn't appear to use the symbol version from GLIBC_2.32\n"; } } open my $fh, '>', "$bin.new" or die "couldn't open $bin.new: $!"; print $fh $bin_contents; chmod 0755, "$bin.new"; my $remaining = `readelf -sW $bin.new | grep 'GLIBC_2.\\(29\\|32\\)'`; if (!$?) { die "additional symbols from GLIBC_2.29 or GLIBC_2.32 found:\n$remaining\n"; } sub parse_readelf_s($bin) { my $readelf = `readelf -sW $bin`; my %ret; for my $line (split "\n", $readelf) { if ($line =~ /^ *[0-9]+:/) { my ($num, $value, $size, $type, $bind, $vis, $ndx, $name, $ver) = split ' ', $line; if (defined $ver) { ($ver) = $ver =~ /\((.*)\)/; } if (defined $num) { ($num) = $num =~ /(.*):/; } $ret{$name} = { num => $num, ver => $ver, } } } %ret } sub parse_readelf_v($bin) { my $readelf = `readelf -V $bin`; my $section; my $file; my %ret; for my $line (split "\n", $readelf) { if ($line =~ /^Version (?:symbols|needs) section '([^']+)'/) { $section = $1; } if ($line =~ /^ Addr: (?:[^ ]+) Offset: ([^ ]+)/) { $ret{$section}{offset} = hex($1); } if ($line =~ /^ [^:]+: Version: [^ ]+ File: ([^ ]+)/) { $file = $1; } if ($line =~ /^ ([^:]+): Name: ([^ ]+) Flags: (?:[^ ]+) Version: (.*)/) { $ret{$section}{$file}{$2} = { offset => hex($1), ver => $3, }; } } %ret }