media/webrtc/trunk/build/mac/change_mach_o_flags.py

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rwxr-xr-x

Ignore runtime configuration files generated during quality assurance.

     1 #!/usr/bin/env python
     2 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
     3 # Use of this source code is governed by a BSD-style license that can be
     4 # found in the LICENSE file.
     6 """Usage: change_mach_o_flags.py [--executable-heap] [--no-pie] <executablepath>
     8 Arranges for the executable at |executable_path| to have its data (heap)
     9 pages protected to prevent execution on Mac OS X 10.7 ("Lion"), and to have
    10 the PIE (position independent executable) bit set to enable ASLR (address
    11 space layout randomization). With --executable-heap or --no-pie, the
    12 respective bits are cleared instead of set, making the heap executable or
    13 disabling PIE/ASLR.
    15 This script is able to operate on thin (single-architecture) Mach-O files
    16 and fat (universal, multi-architecture) files. When operating on fat files,
    17 it will set or clear the bits for each architecture contained therein.
    19 NON-EXECUTABLE HEAP
    21 Traditionally in Mac OS X, 32-bit processes did not have data pages set to
    22 prohibit execution. Although user programs could call mprotect and
    23 mach_vm_protect to deny execution of code in data pages, the kernel would
    24 silently ignore such requests without updating the page tables, and the
    25 hardware would happily execute code on such pages. 64-bit processes were
    26 always given proper hardware protection of data pages. This behavior was
    27 controllable on a system-wide level via the vm.allow_data_exec sysctl, which
    28 is set by default to 1. The bit with value 1 (set by default) allows code
    29 execution on data pages for 32-bit processes, and the bit with value 2
    30 (clear by default) does the same for 64-bit processes.
    32 In Mac OS X 10.7, executables can "opt in" to having hardware protection
    33 against code execution on data pages applied. This is done by setting a new
    34 bit in the |flags| field of an executable's |mach_header|. When
    35 MH_NO_HEAP_EXECUTION is set, proper protections will be applied, regardless
    36 of the setting of vm.allow_data_exec. See xnu-1699.22.73/osfmk/vm/vm_map.c
    37 override_nx and xnu-1699.22.73/bsd/kern/mach_loader.c load_machfile.
    39 The Apple toolchain has been revised to set the MH_NO_HEAP_EXECUTION when
    40 producing executables, provided that -allow_heap_execute is not specified
    41 at link time. Only linkers shipping with Xcode 4.0 and later (ld64-123.2 and
    42 later) have this ability. See ld64-123.2.1/src/ld/Options.cpp
    43 Options::reconfigureDefaults() and
    44 ld64-123.2.1/src/ld/HeaderAndLoadCommands.hpp
    45 HeaderAndLoadCommandsAtom<A>::flags().
    47 This script sets the MH_NO_HEAP_EXECUTION bit on Mach-O executables. It is
    48 intended for use with executables produced by a linker that predates Apple's
    49 modifications to set this bit itself. It is also useful for setting this bit
    50 for non-i386 executables, including x86_64 executables. Apple's linker only
    51 sets it for 32-bit i386 executables, presumably under the assumption that
    52 the value of vm.allow_data_exec is set in stone. However, if someone were to
    53 change vm.allow_data_exec to 2 or 3, 64-bit x86_64 executables would run
    54 without hardware protection against code execution on data pages. This
    55 script can set the bit for x86_64 executables, guaranteeing that they run
    56 with appropriate protection even when vm.allow_data_exec has been tampered
    57 with.
    59 POSITION-INDEPENDENT EXECUTABLES/ADDRESS SPACE LAYOUT RANDOMIZATION
    61 This script sets or clears the MH_PIE bit in an executable's Mach-O header,
    62 enabling or disabling position independence on Mac OS X 10.5 and later.
    63 Processes running position-independent executables have varying levels of
    64 ASLR protection depending on the OS release. The main executable's load
    65 address, shared library load addresess, and the heap and stack base
    66 addresses may be randomized. Position-independent executables are produced
    67 by supplying the -pie flag to the linker (or defeated by supplying -no_pie).
    68 Executables linked with a deployment target of 10.7 or higher have PIE on
    69 by default.
    71 This script is never strictly needed during the build to enable PIE, as all
    72 linkers used are recent enough to support -pie. However, it's used to
    73 disable the PIE bit as needed on already-linked executables.
    74 """
    76 import optparse
    77 import os
    78 import struct
    79 import sys
    82 # <mach-o/fat.h>
    83 FAT_MAGIC = 0xcafebabe
    84 FAT_CIGAM = 0xbebafeca
    86 # <mach-o/loader.h>
    87 MH_MAGIC = 0xfeedface
    88 MH_CIGAM = 0xcefaedfe
    89 MH_MAGIC_64 = 0xfeedfacf
    90 MH_CIGAM_64 = 0xcffaedfe
    91 MH_EXECUTE = 0x2
    92 MH_PIE = 0x00200000
    93 MH_NO_HEAP_EXECUTION = 0x01000000
    96 class MachOError(Exception):
    97   """A class for exceptions thrown by this module."""
    99   pass
   102 def CheckedSeek(file, offset):
   103   """Seeks the file-like object at |file| to offset |offset| and raises a
   104   MachOError if anything funny happens."""
   106   file.seek(offset, os.SEEK_SET)
   107   new_offset = file.tell()
   108   if new_offset != offset:
   109     raise MachOError, \
   110           'seek: expected offset %d, observed %d' % (offset, new_offset)
   113 def CheckedRead(file, count):
   114   """Reads |count| bytes from the file-like |file| object, raising a
   115   MachOError if any other number of bytes is read."""
   117   bytes = file.read(count)
   118   if len(bytes) != count:
   119     raise MachOError, \
   120           'read: expected length %d, observed %d' % (count, len(bytes))
   122   return bytes
   125 def ReadUInt32(file, endian):
   126   """Reads an unsinged 32-bit integer from the file-like |file| object,
   127   treating it as having endianness specified by |endian| (per the |struct|
   128   module), and returns it as a number. Raises a MachOError if the proper
   129   length of data can't be read from |file|."""
   131   bytes = CheckedRead(file, 4)
   133   (uint32,) = struct.unpack(endian + 'I', bytes)
   134   return uint32
   137 def ReadMachHeader(file, endian):
   138   """Reads an entire |mach_header| structure (<mach-o/loader.h>) from the
   139   file-like |file| object, treating it as having endianness specified by
   140   |endian| (per the |struct| module), and returns a 7-tuple of its members
   141   as numbers. Raises a MachOError if the proper length of data can't be read
   142   from |file|."""
   144   bytes = CheckedRead(file, 28)
   146   magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \
   147       struct.unpack(endian + '7I', bytes)
   148   return magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags
   151 def ReadFatArch(file):
   152   """Reads an entire |fat_arch| structure (<mach-o/fat.h>) from the file-like
   153   |file| object, treating it as having endianness specified by |endian|
   154   (per the |struct| module), and returns a 5-tuple of its members as numbers.
   155   Raises a MachOError if the proper length of data can't be read from
   156   |file|."""
   158   bytes = CheckedRead(file, 20)
   160   cputype, cpusubtype, offset, size, align = struct.unpack('>5I', bytes)
   161   return cputype, cpusubtype, offset, size, align
   164 def WriteUInt32(file, uint32, endian):
   165   """Writes |uint32| as an unsinged 32-bit integer to the file-like |file|
   166   object, treating it as having endianness specified by |endian| (per the
   167   |struct| module)."""
   169   bytes = struct.pack(endian + 'I', uint32)
   170   assert len(bytes) == 4
   172   file.write(bytes)
   175 def HandleMachOFile(file, options, offset=0):
   176   """Seeks the file-like |file| object to |offset|, reads its |mach_header|,
   177   and rewrites the header's |flags| field if appropriate. The header's
   178   endianness is detected. Both 32-bit and 64-bit Mach-O headers are supported
   179   (mach_header and mach_header_64). Raises MachOError if used on a header that
   180   does not have a known magic number or is not of type MH_EXECUTE. The
   181   MH_PIE and MH_NO_HEAP_EXECUTION bits are set or cleared in the |flags| field
   182   according to |options| and written to |file| if any changes need to be made.
   183   If already set or clear as specified by |options|, nothing is written."""
   185   CheckedSeek(file, offset)
   186   magic = ReadUInt32(file, '<')
   187   if magic == MH_MAGIC or magic == MH_MAGIC_64:
   188     endian = '<'
   189   elif magic == MH_CIGAM or magic == MH_CIGAM_64:
   190     endian = '>'
   191   else:
   192     raise MachOError, \
   193           'Mach-O file at offset %d has illusion of magic' % offset
   195   CheckedSeek(file, offset)
   196   magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \
   197       ReadMachHeader(file, endian)
   198   assert magic == MH_MAGIC or magic == MH_MAGIC_64
   199   if filetype != MH_EXECUTE:
   200     raise MachOError, \
   201           'Mach-O file at offset %d is type 0x%x, expected MH_EXECUTE' % \
   202               (offset, filetype)
   204   original_flags = flags
   206   if options.no_heap_execution:
   207     flags |= MH_NO_HEAP_EXECUTION
   208   else:
   209     flags &= ~MH_NO_HEAP_EXECUTION
   211   if options.pie:
   212     flags |= MH_PIE
   213   else:
   214     flags &= ~MH_PIE
   216   if flags != original_flags:
   217     CheckedSeek(file, offset + 24)
   218     WriteUInt32(file, flags, endian)
   221 def HandleFatFile(file, options, fat_offset=0):
   222   """Seeks the file-like |file| object to |offset| and loops over its
   223   |fat_header| entries, calling HandleMachOFile for each."""
   225   CheckedSeek(file, fat_offset)
   226   magic = ReadUInt32(file, '>')
   227   assert magic == FAT_MAGIC
   229   nfat_arch = ReadUInt32(file, '>')
   231   for index in xrange(0, nfat_arch):
   232     cputype, cpusubtype, offset, size, align = ReadFatArch(file)
   233     assert size >= 28
   235     # HandleMachOFile will seek around. Come back here after calling it, in
   236     # case it sought.
   237     fat_arch_offset = file.tell()
   238     HandleMachOFile(file, options, offset)
   239     CheckedSeek(file, fat_arch_offset)
   242 def main(me, args):
   243   parser = optparse.OptionParser('%prog [options] <executable_path>')
   244   parser.add_option('--executable-heap', action='store_false',
   245                     dest='no_heap_execution', default=True,
   246                     help='Clear the MH_NO_HEAP_EXECUTION bit')
   247   parser.add_option('--no-pie', action='store_false',
   248                     dest='pie', default=True,
   249                     help='Clear the MH_PIE bit')
   250   (options, loose_args) = parser.parse_args(args)
   251   if len(loose_args) != 1:
   252     parser.print_usage()
   253     return 1
   255   executable_path = loose_args[0]
   256   executable_file = open(executable_path, 'rb+')
   258   magic = ReadUInt32(executable_file, '<')
   259   if magic == FAT_CIGAM:
   260     # Check FAT_CIGAM and not FAT_MAGIC because the read was little-endian.
   261     HandleFatFile(executable_file, options)
   262   elif magic == MH_MAGIC or magic == MH_CIGAM or \
   263       magic == MH_MAGIC_64 or magic == MH_CIGAM_64:
   264     HandleMachOFile(executable_file, options)
   265   else:
   266     raise MachOError, '%s is not a Mach-O or fat file' % executable_file
   268   executable_file.close()
   269   return 0
   272 if __name__ == '__main__':
   273   sys.exit(main(sys.argv[0], sys.argv[1:]))

mercurial