1#!/usr/bin/env python3
2#
3# Copyright (c) 2012 Intel Corporation
4#
5# SPDX-License-Identifier: GPL-2.0-only
6#
7# DESCRIPTION
8# This script is called by the SDK installer script. It replaces the dynamic
9# loader path in all binaries and also fixes the SYSDIR paths/lengths and the
10# location of ld.so.cache in the dynamic loader binary
11#
12# AUTHORS
13# Laurentiu Palcu <laurentiu.palcu@intel.com>
14#
15
16import struct
17import sys
18import stat
19import os
20import re
21import errno
22
23if sys.version < '3':
24    def b(x):
25        return x
26else:
27    def b(x):
28        return x.encode(sys.getfilesystemencoding())
29
30old_prefix = re.compile(b("##DEFAULT_INSTALL_DIR##"))
31
32def get_arch():
33    global endian_prefix
34    f.seek(0)
35    e_ident =f.read(16)
36    ei_mag0,ei_mag1_3,ei_class,ei_data,ei_version = struct.unpack("<B3sBBB9x", e_ident)
37
38    # ei_data = 1 for little-endian & 0 for big-endian
39    if ei_data == 1:
40        endian_prefix = '<'
41    else:
42        endian_prefix = '>'
43
44    if (ei_mag0 != 0x7f and ei_mag1_3 != "ELF") or ei_class == 0:
45        return 0
46
47    if ei_class == 1:
48        return 32
49    elif ei_class == 2:
50        return 64
51
52def parse_elf_header():
53    global e_type, e_machine, e_version, e_entry, e_phoff, e_shoff, e_flags,\
54           e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx
55
56    f.seek(0)
57    elf_header = f.read(64)
58
59    if arch == 32:
60        # 32bit
61        hdr_fmt = endian_prefix + "HHILLLIHHHHHH"
62        hdr_size = 52
63    else:
64        # 64bit
65        hdr_fmt = endian_prefix + "HHIQQQIHHHHHH"
66        hdr_size = 64
67
68    e_type, e_machine, e_version, e_entry, e_phoff, e_shoff, e_flags,\
69    e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx =\
70        struct.unpack(hdr_fmt, elf_header[16:hdr_size])
71
72def change_interpreter(elf_file_name):
73    if arch == 32:
74        ph_fmt = endian_prefix + "IIIIIIII"
75    else:
76        ph_fmt = endian_prefix + "IIQQQQQQ"
77
78    """ look for PT_INTERP section """
79    for i in range(0,e_phnum):
80        f.seek(e_phoff + i * e_phentsize)
81        ph_hdr = f.read(e_phentsize)
82        if arch == 32:
83            # 32bit
84            p_type, p_offset, p_vaddr, p_paddr, p_filesz,\
85                p_memsz, p_flags, p_align = struct.unpack(ph_fmt, ph_hdr)
86        else:
87            # 64bit
88            p_type, p_flags, p_offset, p_vaddr, p_paddr, \
89            p_filesz, p_memsz, p_align = struct.unpack(ph_fmt, ph_hdr)
90
91        """ change interpreter """
92        if p_type == 3:
93            # PT_INTERP section
94            f.seek(p_offset)
95            # External SDKs with mixed pre-compiled binaries should not get
96            # relocated so look for some variant of /lib
97            fname = f.read(11)
98            if fname.startswith(b("/lib/")) or fname.startswith(b("/lib64/")) or \
99               fname.startswith(b("/lib32/")) or fname.startswith(b("/usr/lib32/")) or \
100               fname.startswith(b("/usr/lib32/")) or fname.startswith(b("/usr/lib64/")):
101                break
102            if p_filesz == 0:
103                break
104            if (len(new_dl_path) >= p_filesz):
105                print("ERROR: could not relocate %s, interp size = %i and %i is needed." \
106                    % (elf_file_name, p_memsz, len(new_dl_path) + 1))
107                return False
108            dl_path = new_dl_path + b("\0") * (p_filesz - len(new_dl_path))
109            f.seek(p_offset)
110            f.write(dl_path)
111            break
112    return True
113
114def change_dl_sysdirs(elf_file_name):
115    if arch == 32:
116        sh_fmt = endian_prefix + "IIIIIIIIII"
117    else:
118        sh_fmt = endian_prefix + "IIQQQQIIQQ"
119
120    """ read section string table """
121    f.seek(e_shoff + e_shstrndx * e_shentsize)
122    sh_hdr = f.read(e_shentsize)
123    if arch == 32:
124        sh_offset, sh_size = struct.unpack(endian_prefix + "16xII16x", sh_hdr)
125    else:
126        sh_offset, sh_size = struct.unpack(endian_prefix + "24xQQ24x", sh_hdr)
127
128    f.seek(sh_offset)
129    sh_strtab = f.read(sh_size)
130
131    sysdirs = sysdirs_len = ""
132
133    """ change ld.so.cache path and default libs path for dynamic loader """
134    for i in range(0,e_shnum):
135        f.seek(e_shoff + i * e_shentsize)
136        sh_hdr = f.read(e_shentsize)
137
138        sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link,\
139            sh_info, sh_addralign, sh_entsize = struct.unpack(sh_fmt, sh_hdr)
140
141        name = sh_strtab[sh_name:sh_strtab.find(b("\0"), sh_name)]
142
143        """ look only into SHT_PROGBITS sections """
144        if sh_type == 1:
145            f.seek(sh_offset)
146            """ default library paths cannot be changed on the fly because  """
147            """ the string lengths have to be changed too.                  """
148            if name == b(".sysdirs"):
149                sysdirs = f.read(sh_size)
150                sysdirs_off = sh_offset
151                sysdirs_sect_size = sh_size
152            elif name == b(".sysdirslen"):
153                sysdirslen = f.read(sh_size)
154                sysdirslen_off = sh_offset
155            elif name == b(".ldsocache"):
156                ldsocache_path = f.read(sh_size)
157                new_ldsocache_path = old_prefix.sub(new_prefix, ldsocache_path)
158                new_ldsocache_path = new_ldsocache_path.rstrip(b("\0"))
159                if (len(new_ldsocache_path) >= sh_size):
160                    print("ERROR: could not relocate %s, .ldsocache section size = %i and %i is needed." \
161                    % (elf_file_name, sh_size, len(new_ldsocache_path)))
162                    sys.exit(-1)
163                # pad with zeros
164                new_ldsocache_path += b("\0") * (sh_size - len(new_ldsocache_path))
165                # write it back
166                f.seek(sh_offset)
167                f.write(new_ldsocache_path)
168            elif name == b(".gccrelocprefix"):
169                offset = 0
170                while (offset + 4096) <= sh_size:
171                    path = f.read(4096)
172                    new_path = old_prefix.sub(new_prefix, path)
173                    new_path = new_path.rstrip(b("\0"))
174                    if (len(new_path) >= 4096):
175                        print("ERROR: could not relocate %s, max path size = 4096 and %i is needed." \
176                        % (elf_file_name, len(new_path)))
177                        sys.exit(-1)
178                    # pad with zeros
179                    new_path += b("\0") * (4096 - len(new_path))
180                    #print "Changing %s to %s at %s" % (str(path), str(new_path), str(offset))
181                    # write it back
182                    f.seek(sh_offset + offset)
183                    f.write(new_path)
184                    offset = offset + 4096
185    if sysdirs != "" and sysdirslen != "":
186        paths = sysdirs.split(b("\0"))
187        sysdirs = b("")
188        sysdirslen = b("")
189        for path in paths:
190            """ exit the loop when we encounter first empty string """
191            if path == b(""):
192                break
193
194            new_path = old_prefix.sub(new_prefix, path)
195            sysdirs += new_path + b("\0")
196
197            if arch == 32:
198                sysdirslen += struct.pack("<L", len(new_path))
199            else:
200                sysdirslen += struct.pack("<Q", len(new_path))
201
202        """ pad with zeros """
203        sysdirs += b("\0") * (sysdirs_sect_size - len(sysdirs))
204
205        """ write the sections back """
206        f.seek(sysdirs_off)
207        f.write(sysdirs)
208        f.seek(sysdirslen_off)
209        f.write(sysdirslen)
210
211# MAIN
212if len(sys.argv) < 4:
213    sys.exit(-1)
214
215# In python > 3, strings may also contain Unicode characters. So, convert
216# them to bytes
217if sys.version_info < (3,):
218    new_prefix = sys.argv[1]
219    new_dl_path = sys.argv[2]
220else:
221    new_prefix = sys.argv[1].encode()
222    new_dl_path = sys.argv[2].encode()
223
224executables_list = sys.argv[3:]
225
226errors = False
227for e in executables_list:
228    perms = os.stat(e)[stat.ST_MODE]
229    if os.access(e, os.W_OK|os.R_OK):
230        perms = None
231    else:
232        os.chmod(e, perms|stat.S_IRWXU)
233
234    try:
235        f = open(e, "r+b")
236    except IOError:
237        exctype, ioex = sys.exc_info()[:2]
238        if ioex.errno == errno.ETXTBSY:
239            print("Could not open %s. File used by another process.\nPlease "\
240                  "make sure you exit all processes that might use any SDK "\
241                  "binaries." % e)
242        else:
243            print("Could not open %s: %s(%d)" % (e, ioex.strerror, ioex.errno))
244        sys.exit(-1)
245
246    # Save old size and do a size check at the end. Just a safety measure.
247    old_size = os.path.getsize(e)
248    if old_size >= 64:
249        arch = get_arch()
250        if arch:
251            parse_elf_header()
252            if not change_interpreter(e):
253                errors = True
254            change_dl_sysdirs(e)
255
256    """ change permissions back """
257    if perms:
258        os.chmod(e, perms)
259
260    f.close()
261
262    if old_size != os.path.getsize(e):
263        print("New file size for %s is different. Looks like a relocation error!", e)
264        sys.exit(-1)
265
266if errors:
267    print("Relocation of one or more executables failed.")
268    sys.exit(-1)
269