1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/media/webrtc/trunk/build/mac/change_mach_o_flags.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,273 @@ 1.4 +#!/usr/bin/env python 1.5 +# Copyright (c) 2011 The Chromium Authors. All rights reserved. 1.6 +# Use of this source code is governed by a BSD-style license that can be 1.7 +# found in the LICENSE file. 1.8 + 1.9 +"""Usage: change_mach_o_flags.py [--executable-heap] [--no-pie] <executablepath> 1.10 + 1.11 +Arranges for the executable at |executable_path| to have its data (heap) 1.12 +pages protected to prevent execution on Mac OS X 10.7 ("Lion"), and to have 1.13 +the PIE (position independent executable) bit set to enable ASLR (address 1.14 +space layout randomization). With --executable-heap or --no-pie, the 1.15 +respective bits are cleared instead of set, making the heap executable or 1.16 +disabling PIE/ASLR. 1.17 + 1.18 +This script is able to operate on thin (single-architecture) Mach-O files 1.19 +and fat (universal, multi-architecture) files. When operating on fat files, 1.20 +it will set or clear the bits for each architecture contained therein. 1.21 + 1.22 +NON-EXECUTABLE HEAP 1.23 + 1.24 +Traditionally in Mac OS X, 32-bit processes did not have data pages set to 1.25 +prohibit execution. Although user programs could call mprotect and 1.26 +mach_vm_protect to deny execution of code in data pages, the kernel would 1.27 +silently ignore such requests without updating the page tables, and the 1.28 +hardware would happily execute code on such pages. 64-bit processes were 1.29 +always given proper hardware protection of data pages. This behavior was 1.30 +controllable on a system-wide level via the vm.allow_data_exec sysctl, which 1.31 +is set by default to 1. The bit with value 1 (set by default) allows code 1.32 +execution on data pages for 32-bit processes, and the bit with value 2 1.33 +(clear by default) does the same for 64-bit processes. 1.34 + 1.35 +In Mac OS X 10.7, executables can "opt in" to having hardware protection 1.36 +against code execution on data pages applied. This is done by setting a new 1.37 +bit in the |flags| field of an executable's |mach_header|. When 1.38 +MH_NO_HEAP_EXECUTION is set, proper protections will be applied, regardless 1.39 +of the setting of vm.allow_data_exec. See xnu-1699.22.73/osfmk/vm/vm_map.c 1.40 +override_nx and xnu-1699.22.73/bsd/kern/mach_loader.c load_machfile. 1.41 + 1.42 +The Apple toolchain has been revised to set the MH_NO_HEAP_EXECUTION when 1.43 +producing executables, provided that -allow_heap_execute is not specified 1.44 +at link time. Only linkers shipping with Xcode 4.0 and later (ld64-123.2 and 1.45 +later) have this ability. See ld64-123.2.1/src/ld/Options.cpp 1.46 +Options::reconfigureDefaults() and 1.47 +ld64-123.2.1/src/ld/HeaderAndLoadCommands.hpp 1.48 +HeaderAndLoadCommandsAtom<A>::flags(). 1.49 + 1.50 +This script sets the MH_NO_HEAP_EXECUTION bit on Mach-O executables. It is 1.51 +intended for use with executables produced by a linker that predates Apple's 1.52 +modifications to set this bit itself. It is also useful for setting this bit 1.53 +for non-i386 executables, including x86_64 executables. Apple's linker only 1.54 +sets it for 32-bit i386 executables, presumably under the assumption that 1.55 +the value of vm.allow_data_exec is set in stone. However, if someone were to 1.56 +change vm.allow_data_exec to 2 or 3, 64-bit x86_64 executables would run 1.57 +without hardware protection against code execution on data pages. This 1.58 +script can set the bit for x86_64 executables, guaranteeing that they run 1.59 +with appropriate protection even when vm.allow_data_exec has been tampered 1.60 +with. 1.61 + 1.62 +POSITION-INDEPENDENT EXECUTABLES/ADDRESS SPACE LAYOUT RANDOMIZATION 1.63 + 1.64 +This script sets or clears the MH_PIE bit in an executable's Mach-O header, 1.65 +enabling or disabling position independence on Mac OS X 10.5 and later. 1.66 +Processes running position-independent executables have varying levels of 1.67 +ASLR protection depending on the OS release. The main executable's load 1.68 +address, shared library load addresess, and the heap and stack base 1.69 +addresses may be randomized. Position-independent executables are produced 1.70 +by supplying the -pie flag to the linker (or defeated by supplying -no_pie). 1.71 +Executables linked with a deployment target of 10.7 or higher have PIE on 1.72 +by default. 1.73 + 1.74 +This script is never strictly needed during the build to enable PIE, as all 1.75 +linkers used are recent enough to support -pie. However, it's used to 1.76 +disable the PIE bit as needed on already-linked executables. 1.77 +""" 1.78 + 1.79 +import optparse 1.80 +import os 1.81 +import struct 1.82 +import sys 1.83 + 1.84 + 1.85 +# <mach-o/fat.h> 1.86 +FAT_MAGIC = 0xcafebabe 1.87 +FAT_CIGAM = 0xbebafeca 1.88 + 1.89 +# <mach-o/loader.h> 1.90 +MH_MAGIC = 0xfeedface 1.91 +MH_CIGAM = 0xcefaedfe 1.92 +MH_MAGIC_64 = 0xfeedfacf 1.93 +MH_CIGAM_64 = 0xcffaedfe 1.94 +MH_EXECUTE = 0x2 1.95 +MH_PIE = 0x00200000 1.96 +MH_NO_HEAP_EXECUTION = 0x01000000 1.97 + 1.98 + 1.99 +class MachOError(Exception): 1.100 + """A class for exceptions thrown by this module.""" 1.101 + 1.102 + pass 1.103 + 1.104 + 1.105 +def CheckedSeek(file, offset): 1.106 + """Seeks the file-like object at |file| to offset |offset| and raises a 1.107 + MachOError if anything funny happens.""" 1.108 + 1.109 + file.seek(offset, os.SEEK_SET) 1.110 + new_offset = file.tell() 1.111 + if new_offset != offset: 1.112 + raise MachOError, \ 1.113 + 'seek: expected offset %d, observed %d' % (offset, new_offset) 1.114 + 1.115 + 1.116 +def CheckedRead(file, count): 1.117 + """Reads |count| bytes from the file-like |file| object, raising a 1.118 + MachOError if any other number of bytes is read.""" 1.119 + 1.120 + bytes = file.read(count) 1.121 + if len(bytes) != count: 1.122 + raise MachOError, \ 1.123 + 'read: expected length %d, observed %d' % (count, len(bytes)) 1.124 + 1.125 + return bytes 1.126 + 1.127 + 1.128 +def ReadUInt32(file, endian): 1.129 + """Reads an unsinged 32-bit integer from the file-like |file| object, 1.130 + treating it as having endianness specified by |endian| (per the |struct| 1.131 + module), and returns it as a number. Raises a MachOError if the proper 1.132 + length of data can't be read from |file|.""" 1.133 + 1.134 + bytes = CheckedRead(file, 4) 1.135 + 1.136 + (uint32,) = struct.unpack(endian + 'I', bytes) 1.137 + return uint32 1.138 + 1.139 + 1.140 +def ReadMachHeader(file, endian): 1.141 + """Reads an entire |mach_header| structure (<mach-o/loader.h>) from the 1.142 + file-like |file| object, treating it as having endianness specified by 1.143 + |endian| (per the |struct| module), and returns a 7-tuple of its members 1.144 + as numbers. Raises a MachOError if the proper length of data can't be read 1.145 + from |file|.""" 1.146 + 1.147 + bytes = CheckedRead(file, 28) 1.148 + 1.149 + magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \ 1.150 + struct.unpack(endian + '7I', bytes) 1.151 + return magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags 1.152 + 1.153 + 1.154 +def ReadFatArch(file): 1.155 + """Reads an entire |fat_arch| structure (<mach-o/fat.h>) from the file-like 1.156 + |file| object, treating it as having endianness specified by |endian| 1.157 + (per the |struct| module), and returns a 5-tuple of its members as numbers. 1.158 + Raises a MachOError if the proper length of data can't be read from 1.159 + |file|.""" 1.160 + 1.161 + bytes = CheckedRead(file, 20) 1.162 + 1.163 + cputype, cpusubtype, offset, size, align = struct.unpack('>5I', bytes) 1.164 + return cputype, cpusubtype, offset, size, align 1.165 + 1.166 + 1.167 +def WriteUInt32(file, uint32, endian): 1.168 + """Writes |uint32| as an unsinged 32-bit integer to the file-like |file| 1.169 + object, treating it as having endianness specified by |endian| (per the 1.170 + |struct| module).""" 1.171 + 1.172 + bytes = struct.pack(endian + 'I', uint32) 1.173 + assert len(bytes) == 4 1.174 + 1.175 + file.write(bytes) 1.176 + 1.177 + 1.178 +def HandleMachOFile(file, options, offset=0): 1.179 + """Seeks the file-like |file| object to |offset|, reads its |mach_header|, 1.180 + and rewrites the header's |flags| field if appropriate. The header's 1.181 + endianness is detected. Both 32-bit and 64-bit Mach-O headers are supported 1.182 + (mach_header and mach_header_64). Raises MachOError if used on a header that 1.183 + does not have a known magic number or is not of type MH_EXECUTE. The 1.184 + MH_PIE and MH_NO_HEAP_EXECUTION bits are set or cleared in the |flags| field 1.185 + according to |options| and written to |file| if any changes need to be made. 1.186 + If already set or clear as specified by |options|, nothing is written.""" 1.187 + 1.188 + CheckedSeek(file, offset) 1.189 + magic = ReadUInt32(file, '<') 1.190 + if magic == MH_MAGIC or magic == MH_MAGIC_64: 1.191 + endian = '<' 1.192 + elif magic == MH_CIGAM or magic == MH_CIGAM_64: 1.193 + endian = '>' 1.194 + else: 1.195 + raise MachOError, \ 1.196 + 'Mach-O file at offset %d has illusion of magic' % offset 1.197 + 1.198 + CheckedSeek(file, offset) 1.199 + magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \ 1.200 + ReadMachHeader(file, endian) 1.201 + assert magic == MH_MAGIC or magic == MH_MAGIC_64 1.202 + if filetype != MH_EXECUTE: 1.203 + raise MachOError, \ 1.204 + 'Mach-O file at offset %d is type 0x%x, expected MH_EXECUTE' % \ 1.205 + (offset, filetype) 1.206 + 1.207 + original_flags = flags 1.208 + 1.209 + if options.no_heap_execution: 1.210 + flags |= MH_NO_HEAP_EXECUTION 1.211 + else: 1.212 + flags &= ~MH_NO_HEAP_EXECUTION 1.213 + 1.214 + if options.pie: 1.215 + flags |= MH_PIE 1.216 + else: 1.217 + flags &= ~MH_PIE 1.218 + 1.219 + if flags != original_flags: 1.220 + CheckedSeek(file, offset + 24) 1.221 + WriteUInt32(file, flags, endian) 1.222 + 1.223 + 1.224 +def HandleFatFile(file, options, fat_offset=0): 1.225 + """Seeks the file-like |file| object to |offset| and loops over its 1.226 + |fat_header| entries, calling HandleMachOFile for each.""" 1.227 + 1.228 + CheckedSeek(file, fat_offset) 1.229 + magic = ReadUInt32(file, '>') 1.230 + assert magic == FAT_MAGIC 1.231 + 1.232 + nfat_arch = ReadUInt32(file, '>') 1.233 + 1.234 + for index in xrange(0, nfat_arch): 1.235 + cputype, cpusubtype, offset, size, align = ReadFatArch(file) 1.236 + assert size >= 28 1.237 + 1.238 + # HandleMachOFile will seek around. Come back here after calling it, in 1.239 + # case it sought. 1.240 + fat_arch_offset = file.tell() 1.241 + HandleMachOFile(file, options, offset) 1.242 + CheckedSeek(file, fat_arch_offset) 1.243 + 1.244 + 1.245 +def main(me, args): 1.246 + parser = optparse.OptionParser('%prog [options] <executable_path>') 1.247 + parser.add_option('--executable-heap', action='store_false', 1.248 + dest='no_heap_execution', default=True, 1.249 + help='Clear the MH_NO_HEAP_EXECUTION bit') 1.250 + parser.add_option('--no-pie', action='store_false', 1.251 + dest='pie', default=True, 1.252 + help='Clear the MH_PIE bit') 1.253 + (options, loose_args) = parser.parse_args(args) 1.254 + if len(loose_args) != 1: 1.255 + parser.print_usage() 1.256 + return 1 1.257 + 1.258 + executable_path = loose_args[0] 1.259 + executable_file = open(executable_path, 'rb+') 1.260 + 1.261 + magic = ReadUInt32(executable_file, '<') 1.262 + if magic == FAT_CIGAM: 1.263 + # Check FAT_CIGAM and not FAT_MAGIC because the read was little-endian. 1.264 + HandleFatFile(executable_file, options) 1.265 + elif magic == MH_MAGIC or magic == MH_CIGAM or \ 1.266 + magic == MH_MAGIC_64 or magic == MH_CIGAM_64: 1.267 + HandleMachOFile(executable_file, options) 1.268 + else: 1.269 + raise MachOError, '%s is not a Mach-O or fat file' % executable_file 1.270 + 1.271 + executable_file.close() 1.272 + return 0 1.273 + 1.274 + 1.275 +if __name__ == '__main__': 1.276 + sys.exit(main(sys.argv[0], sys.argv[1:]))