patchelf 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +70 -12
- data/bin/patchelf.rb +1 -0
- data/lib/patchelf.rb +2 -0
- data/lib/patchelf/cli.rb +52 -8
- data/lib/patchelf/helper.rb +8 -6
- data/lib/patchelf/logger.rb +2 -0
- data/lib/patchelf/mm.rb +95 -78
- data/lib/patchelf/patcher.rb +105 -138
- data/lib/patchelf/saver.rb +284 -0
- data/lib/patchelf/version.rb +3 -1
- metadata +4 -5
- data/lib/patchelf/interval.rb +0 -30
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d0268e11ae1f743826ab85b81245876d13ec987f4bf9b530f399260447b939aa
         | 
| 4 | 
            +
              data.tar.gz: f8fca7603e33bdea3d44c328076a64a37172d61c49d1fd7a7915fe5b4d9ddba3
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 462df528984a4f51efb4f9b0f589b0378ff4b28a1008357c0f7b7b4cdaa4a41739402c3821cc6b06a1dccd7292e02c1f2e1d9513a7926e9708b56e0abc66ba84
         | 
| 7 | 
            +
              data.tar.gz: acddd987c408cf26dc323a6873d5db81aba3635ffba5e9eb6a3d4ca76f2d0d5cfe395739073f0594307274be756e5f270b162cbc952ac5d4797672b321ddeb5f
         | 
    
        data/README.md
    CHANGED
    
    | @@ -12,19 +12,34 @@ Implements features of NixOS/patchelf in pure Ruby. | |
| 12 12 |  | 
| 13 13 | 
             
            ## Installation
         | 
| 14 14 |  | 
| 15 | 
            -
             | 
| 15 | 
            +
            Available on RubyGems.org!
         | 
| 16 | 
            +
            ```
         | 
| 17 | 
            +
            $ gem install patchelf
         | 
| 18 | 
            +
            ```
         | 
| 16 19 |  | 
| 17 20 | 
             
            ## Usage
         | 
| 18 21 |  | 
| 19 22 | 
             
            ```
         | 
| 20 23 | 
             
            $ patchelf.rb
         | 
| 21 24 | 
             
            # Usage: patchelf.rb <commands> FILENAME [OUTPUT_FILE]
         | 
| 22 | 
            -
            #         -- | 
| 23 | 
            -
            #         -- | 
| 24 | 
            -
            #         -- | 
| 25 | 
            -
            #         -- | 
| 25 | 
            +
            #         --print-interpreter, --pi    Show interpreter's name.
         | 
| 26 | 
            +
            #         --print-needed, --pn         Show needed libraries specified in DT_NEEDED.
         | 
| 27 | 
            +
            #         --print-runpath, --pr        Show the path specified in DT_RUNPATH.
         | 
| 28 | 
            +
            #         --print-soname, --ps         Show soname specified in DT_SONAME.
         | 
| 29 | 
            +
            #         --set-interpreter, --interp INTERP
         | 
| 26 30 | 
             
            #                                      Set interpreter's name.
         | 
| 27 | 
            -
            #         -- | 
| 31 | 
            +
            #         --set-needed, --needed LIB1,LIB2,LIB3
         | 
| 32 | 
            +
            #                                      Set needed libraries, this will remove all existent needed libraries.
         | 
| 33 | 
            +
            #         --add-needed LIB             Append a new needed library.
         | 
| 34 | 
            +
            #         --remove-needed LIB          Remove a needed library.
         | 
| 35 | 
            +
            #         --replace-needed LIB1,LIB2   Replace needed library LIB1 as LIB2.
         | 
| 36 | 
            +
            #         --set-runpath, --runpath PATH
         | 
| 37 | 
            +
            #                                      Set the path of runpath.
         | 
| 38 | 
            +
            #         --force-rpath                According to the ld.so docs, DT_RPATH is obsolete,
         | 
| 39 | 
            +
            #                                      patchelf.rb will always try to get/set DT_RUNPATH first.
         | 
| 40 | 
            +
            #                                      Use this option to force every operations related to runpath (e.g. --runpath)
         | 
| 41 | 
            +
            #                                      to consider 'DT_RPATH' instead of 'DT_RUNPATH'.
         | 
| 42 | 
            +
            #         --set-soname, --so SONAME    Set name of a shared library.
         | 
| 28 43 | 
             
            #         --version                    Show current gem's version.
         | 
| 29 44 |  | 
| 30 45 | 
             
            ```
         | 
| @@ -32,8 +47,8 @@ $ patchelf.rb | |
| 32 47 | 
             
            ### Display information
         | 
| 33 48 | 
             
            ```
         | 
| 34 49 | 
             
            $ patchelf.rb --print-interpreter --print-needed /bin/ls
         | 
| 35 | 
            -
            #  | 
| 36 | 
            -
            #  | 
| 50 | 
            +
            # interpreter: /lib64/ld-linux-x86-64.so.2
         | 
| 51 | 
            +
            # needed: libselinux.so.1 libc.so.6
         | 
| 37 52 |  | 
| 38 53 | 
             
            ```
         | 
| 39 54 |  | 
| @@ -47,11 +62,54 @@ $ file ls.patch | |
| 47 62 |  | 
| 48 63 | 
             
            ```
         | 
| 49 64 |  | 
| 65 | 
            +
            ### Modify dependency libraries
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            #### Add
         | 
| 68 | 
            +
            ```
         | 
| 69 | 
            +
            $ patchelf.rb --add-needed libnew.so /bin/ls ls.patch
         | 
| 70 | 
            +
            ```
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            #### Remove
         | 
| 73 | 
            +
            ```
         | 
| 74 | 
            +
            $ patchelf.rb --remove-needed libc.so.6 /bin/ls ls.patch
         | 
| 75 | 
            +
            ```
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            #### Replace
         | 
| 78 | 
            +
            ```
         | 
| 79 | 
            +
            $ patchelf.rb --replace-needed libc.so.6,libcnew.so.6 /bin/ls ls.patch
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            $ readelf -d ls.patch | grep NEEDED
         | 
| 82 | 
            +
            #  0x0000000000000001 (NEEDED)             Shared library: [libselinux.so.1]
         | 
| 83 | 
            +
            #  0x0000000000000001 (NEEDED)             Shared library: [libcnew.so.6]
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            ```
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            #### Set directly
         | 
| 88 | 
            +
            ```
         | 
| 89 | 
            +
            $ patchelf.rb --needed a.so,b.so,c.so /bin/ls ls.patch
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            $ readelf -d ls.patch | grep NEEDED
         | 
| 92 | 
            +
            #  0x0000000000000001 (NEEDED)             Shared library: [a.so]
         | 
| 93 | 
            +
            #  0x0000000000000001 (NEEDED)             Shared library: [b.so]
         | 
| 94 | 
            +
            #  0x0000000000000001 (NEEDED)             Shared library: [c.so]
         | 
| 95 | 
            +
             | 
| 96 | 
            +
            ```
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            ### Set RUNPATH of an executable
         | 
| 99 | 
            +
            ```
         | 
| 100 | 
            +
            $ patchelf.rb --runpath . /bin/ls ls.patch
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            $ readelf -d ls.patch | grep RUNPATH
         | 
| 103 | 
            +
            #  0x000000000000001d (RUNPATH)            Library runpath: [.]
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            ```
         | 
| 106 | 
            +
             | 
| 50 107 | 
             
            ### Change SONAME of a shared library
         | 
| 51 108 | 
             
            ```
         | 
| 52 | 
            -
            $ patchelf.rb -- | 
| 109 | 
            +
            $ patchelf.rb --so libc.so.217 /lib/x86_64-linux-gnu/libc.so.6 libc.patch
         | 
| 53 110 |  | 
| 54 | 
            -
            $ readelf -d libc. | 
| 111 | 
            +
            $ readelf -d libc.patch | grep SONAME
         | 
| 112 | 
            +
            #  0x000000000000000e (SONAME)             Library soname: [libc.so.217]
         | 
| 55 113 |  | 
| 56 114 | 
             
            ```
         | 
| 57 115 |  | 
| @@ -60,11 +118,11 @@ $ readelf -d libc.patched | grep SONAME | |
| 60 118 | 
             
            require 'patchelf'
         | 
| 61 119 |  | 
| 62 120 | 
             
            patcher = PatchELF::Patcher.new('/bin/ls')
         | 
| 63 | 
            -
            patcher. | 
| 121 | 
            +
            patcher.interpreter
         | 
| 64 122 | 
             
            #=> "/lib64/ld-linux-x86-64.so.2"
         | 
| 65 123 |  | 
| 66 124 | 
             
            patcher.interpreter = '/lib/AAAA.so.2'
         | 
| 67 | 
            -
            patcher. | 
| 125 | 
            +
            patcher.interpreter
         | 
| 68 126 | 
             
            #=> "/lib/AAAA.so.2"
         | 
| 69 127 |  | 
| 70 128 | 
             
            patcher.save('ls.patch')
         | 
    
        data/bin/patchelf.rb
    CHANGED
    
    
    
        data/lib/patchelf.rb
    CHANGED
    
    
    
        data/lib/patchelf/cli.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'optparse'
         | 
| 2 4 |  | 
| 3 5 | 
             
            require 'patchelf/patcher'
         | 
| @@ -25,25 +27,32 @@ module PatchELF | |
| 25 27 | 
             
                def work(argv)
         | 
| 26 28 | 
             
                  @options = {
         | 
| 27 29 | 
             
                    set: {},
         | 
| 28 | 
            -
                    print: []
         | 
| 30 | 
            +
                    print: [],
         | 
| 31 | 
            +
                    needed: []
         | 
| 29 32 | 
             
                  }
         | 
| 30 33 | 
             
                  return $stdout.puts "PatchELF Version #{PatchELF::VERSION}" if argv.include?('--version')
         | 
| 31 34 | 
             
                  return $stdout.puts option_parser unless parse(argv)
         | 
| 32 35 |  | 
| 33 36 | 
             
                  # Now the options are (hopefully) valid, let's process the ELF file.
         | 
| 34 37 | 
             
                  patcher = PatchELF::Patcher.new(@options[:in_file])
         | 
| 38 | 
            +
                  patcher.use_rpath! if @options[:force_rpath]
         | 
| 35 39 | 
             
                  # TODO: Handle ELFTools::ELFError
         | 
| 36 40 | 
             
                  @options[:print].uniq.each do |s|
         | 
| 37 | 
            -
                    content = patcher. | 
| 41 | 
            +
                    content = patcher.__send__(s)
         | 
| 38 42 | 
             
                    next if content.nil?
         | 
| 39 43 |  | 
| 40 | 
            -
                     | 
| 44 | 
            +
                    s = :rpath if @options[:force_rpath] && s == :runpath
         | 
| 45 | 
            +
                    $stdout.puts "#{s}: #{Array(content).join(' ')}"
         | 
| 41 46 | 
             
                  end
         | 
| 42 47 |  | 
| 43 48 | 
             
                  @options[:set].each do |sym, val|
         | 
| 44 49 | 
             
                    patcher.__send__("#{sym}=".to_sym, val)
         | 
| 45 50 | 
             
                  end
         | 
| 46 51 |  | 
| 52 | 
            +
                  @options[:needed].each do |type, val|
         | 
| 53 | 
            +
                    patcher.__send__("#{type}_needed".to_sym, *val)
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 47 56 | 
             
                  patcher.save(@options[:out_file])
         | 
| 48 57 | 
             
                end
         | 
| 49 58 |  | 
| @@ -62,23 +71,58 @@ module PatchELF | |
| 62 71 | 
             
                  @option_parser ||= OptionParser.new do |opts|
         | 
| 63 72 | 
             
                    opts.banner = USAGE
         | 
| 64 73 |  | 
| 65 | 
            -
                    opts.on('-- | 
| 74 | 
            +
                    opts.on('--print-interpreter', '--pi', 'Show interpreter\'s name.') do
         | 
| 66 75 | 
             
                      @options[:print] << :interpreter
         | 
| 67 76 | 
             
                    end
         | 
| 68 77 |  | 
| 69 | 
            -
                    opts.on('-- | 
| 78 | 
            +
                    opts.on('--print-needed', '--pn', 'Show needed libraries specified in DT_NEEDED.') do
         | 
| 70 79 | 
             
                      @options[:print] << :needed
         | 
| 71 80 | 
             
                    end
         | 
| 72 81 |  | 
| 73 | 
            -
                    opts.on('-- | 
| 82 | 
            +
                    opts.on('--print-runpath', '--pr', 'Show the path specified in DT_RUNPATH.') do
         | 
| 83 | 
            +
                      @options[:print] << :runpath
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    opts.on('--print-soname', '--ps', 'Show soname specified in DT_SONAME.') do
         | 
| 74 87 | 
             
                      @options[:print] << :soname
         | 
| 75 88 | 
             
                    end
         | 
| 76 89 |  | 
| 77 | 
            -
                    opts.on('-- | 
| 90 | 
            +
                    opts.on('--set-interpreter INTERP', '--interp INTERP', 'Set interpreter\'s name.') do |interp|
         | 
| 78 91 | 
             
                      @options[:set][:interpreter] = interp
         | 
| 79 92 | 
             
                    end
         | 
| 80 93 |  | 
| 81 | 
            -
                    opts.on('-- | 
| 94 | 
            +
                    opts.on('--set-needed LIB1,LIB2,LIB3', '--needed LIB1,LIB2,LIB3', Array,
         | 
| 95 | 
            +
                            'Set needed libraries, this will remove all existent needed libraries.') do |needs|
         | 
| 96 | 
            +
                      @options[:set][:needed] = needs
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    opts.on('--add-needed LIB', 'Append a new needed library.') do |lib|
         | 
| 100 | 
            +
                      @options[:needed] << [:add, lib]
         | 
| 101 | 
            +
                    end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                    opts.on('--remove-needed LIB', 'Remove a needed library.') do |lib|
         | 
| 104 | 
            +
                      @options[:needed] << [:remove, lib]
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                    opts.on('--replace-needed LIB1,LIB2', Array, 'Replace needed library LIB1 as LIB2.') do |libs|
         | 
| 108 | 
            +
                      @options[:needed] << [:replace, libs]
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    opts.on('--set-runpath PATH', '--runpath PATH', 'Set the path of runpath.') do |path|
         | 
| 112 | 
            +
                      @options[:set][:runpath] = path
         | 
| 113 | 
            +
                    end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                    opts.on(
         | 
| 116 | 
            +
                      '--force-rpath',
         | 
| 117 | 
            +
                      'According to the ld.so docs, DT_RPATH is obsolete,',
         | 
| 118 | 
            +
                      "#{SCRIPT_NAME} will always try to get/set DT_RUNPATH first.",
         | 
| 119 | 
            +
                      'Use this option to force every operations related to runpath (e.g. --runpath)',
         | 
| 120 | 
            +
                      'to consider \'DT_RPATH\' instead of \'DT_RUNPATH\'.'
         | 
| 121 | 
            +
                    ) do
         | 
| 122 | 
            +
                      @options[:force_rpath] = true
         | 
| 123 | 
            +
                    end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                    opts.on('--set-soname SONAME', '--so SONAME', 'Set name of a shared library.') do |soname|
         | 
| 82 126 | 
             
                      @options[:set][:soname] = soname
         | 
| 83 127 | 
             
                    end
         | 
| 84 128 |  | 
    
        data/lib/patchelf/helper.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module PatchELF
         | 
| 2 4 | 
             
              # Helper methods for internal usage.
         | 
| 3 5 | 
             
              module Helper
         | 
| @@ -40,11 +42,11 @@ module PatchELF | |
| 40 42 | 
             
                # @return [Integer]
         | 
| 41 43 | 
             
                #   Aligned result.
         | 
| 42 44 | 
             
                # @example
         | 
| 43 | 
            -
                #    | 
| 45 | 
            +
                #   aligndown(0x1234)
         | 
| 44 46 | 
             
                #   #=> 4096
         | 
| 45 | 
            -
                #    | 
| 47 | 
            +
                #   aligndown(0x33, 0x20)
         | 
| 46 48 | 
             
                #   #=> 32
         | 
| 47 | 
            -
                #    | 
| 49 | 
            +
                #   aligndown(0x10, 0x8)
         | 
| 48 50 | 
             
                #   #=> 16
         | 
| 49 51 | 
             
                def aligndown(val, align = PAGE_SIZE)
         | 
| 50 52 | 
             
                  val - (val & (align - 1))
         | 
| @@ -55,11 +57,11 @@ module PatchELF | |
| 55 57 | 
             
                # @return [Integer]
         | 
| 56 58 | 
             
                #   Aligned result.
         | 
| 57 59 | 
             
                # @example
         | 
| 58 | 
            -
                #    | 
| 60 | 
            +
                #   alignup(0x1234)
         | 
| 59 61 | 
             
                #   #=> 8192
         | 
| 60 | 
            -
                #    | 
| 62 | 
            +
                #   alignup(0x33, 0x20)
         | 
| 61 63 | 
             
                #   #=> 64
         | 
| 62 | 
            -
                #    | 
| 64 | 
            +
                #   alignup(0x10, 0x8)
         | 
| 63 65 | 
             
                #   #=> 16
         | 
| 64 66 | 
             
                def alignup(val, align = PAGE_SIZE)
         | 
| 65 67 | 
             
                  (val & (align - 1)).zero? ? val : (aligndown(val, align) + align)
         | 
    
        data/lib/patchelf/logger.rb
    CHANGED
    
    
    
        data/lib/patchelf/mm.rb
    CHANGED
    
    | @@ -1,11 +1,13 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'patchelf/helper'
         | 
| 2 | 
            -
            require 'patchelf/interval'
         | 
| 3 4 |  | 
| 4 5 | 
             
            module PatchELF
         | 
| 5 6 | 
             
              # Memory management, provides malloc/free to allocate LOAD segments.
         | 
| 7 | 
            +
              # @private
         | 
| 6 8 | 
             
              class MM
         | 
| 7 | 
            -
                attr_reader :extend_size # @return [Integer]
         | 
| 8 | 
            -
                attr_reader :threshold # @return [Integer]
         | 
| 9 | 
            +
                attr_reader :extend_size # @return [Integer] The size extended.
         | 
| 10 | 
            +
                attr_reader :threshold # @return [Integer] Where the file start to be extended.
         | 
| 9 11 |  | 
| 10 12 | 
             
                # Instantiate a {MM} object.
         | 
| 11 13 | 
             
                # @param [ELFTools::ELFFile] elf
         | 
| @@ -21,7 +23,7 @@ module PatchELF | |
| 21 23 | 
             
                # @yieldreturn [void]
         | 
| 22 24 | 
             
                #   One can only do the following things in the block:
         | 
| 23 25 | 
             
                #   1. Set ELF headers' attributes (with ELFTools)
         | 
| 24 | 
            -
                #   2. Invoke { | 
| 26 | 
            +
                #   2. Invoke {Saver#inline_patch}
         | 
| 25 27 | 
             
                def malloc(size, &block)
         | 
| 26 28 | 
             
                  # TODO: check size > 0
         | 
| 27 29 | 
             
                  @request << [size, block]
         | 
| @@ -32,39 +34,21 @@ module PatchELF | |
| 32 34 | 
             
                def dispatch!
         | 
| 33 35 | 
             
                  return if @request.empty?
         | 
| 34 36 |  | 
| 35 | 
            -
                  request_size = @request.map(&:first).inject(0, :+)
         | 
| 36 | 
            -
                  #  | 
| 37 | 
            -
             | 
| 38 | 
            -
                  # We' | 
| 39 | 
            -
                  #  | 
| 40 | 
            -
                  #  | 
| 41 | 
            -
                   | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
                    # |  1      | |  2  |
         | 
| 51 | 
            -
                    # |  1      |    |  2  |
         | 
| 52 | 
            -
                    # This is really dangerous..
         | 
| 53 | 
            -
                    # We have to check all p_offset / sh_offset
         | 
| 54 | 
            -
                    # 1. Use ELFTools to patch all headers
         | 
| 55 | 
            -
                    # 2. Mark the extended size, inline_patch will behave different after this.
         | 
| 56 | 
            -
                    # 3. Invoke block.call, which might copy tables and (not-allow-to-patch) strings into the gap
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                    @threshold = load_segments[1].file_head
         | 
| 59 | 
            -
                    # 1.file_tail + request_size <= 2.file_head + 0x1000x
         | 
| 60 | 
            -
                    @extend_size = PatchELF::Helper.alignup(request_size - gap_between_load.size)
         | 
| 61 | 
            -
                    shift_attributes
         | 
| 62 | 
            -
             | 
| 63 | 
            -
                    invoke_callbacks
         | 
| 64 | 
            -
                    grow_first_load(request_size)
         | 
| 65 | 
            -
                    # else
         | 
| 66 | 
            -
                    # This can happen in 32bit
         | 
| 67 | 
            -
                  end
         | 
| 37 | 
            +
                  @request_size = @request.map(&:first).inject(0, :+)
         | 
| 38 | 
            +
                  # The malloc-ed area must be 'rw-' since the dynamic table will be modified during runtime.
         | 
| 39 | 
            +
                  # Find all LOADs and calculate their f-gaps and m-gaps.
         | 
| 40 | 
            +
                  # We prefer f-gap since it doesn't need move the whole binaries.
         | 
| 41 | 
            +
                  # 1. Find if any f-gap has enough size, and one of the LOAD next to it is 'rw-'.
         | 
| 42 | 
            +
                  #   - expand (forwardlly), only need to change the attribute of LOAD.
         | 
| 43 | 
            +
                  # 2. Do 1. again but consider m-gaps instead.
         | 
| 44 | 
            +
                  #   - expand (forwardlly), need to modify all section headers.
         | 
| 45 | 
            +
                  # 3. We have to create a new LOAD, now we need to expand the first LOAD for putting new segment header.
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  # First of all we check if there're less than two LOADs.
         | 
| 48 | 
            +
                  abnormal_elf('No LOAD segment found, not an executable.') if load_segments.empty?
         | 
| 49 | 
            +
                  # TODO: Handle only one LOAD. (be careful if memsz > filesz)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  fgap_method || mgap_method || new_load_method
         | 
| 68 52 | 
             
                end
         | 
| 69 53 |  | 
| 70 54 | 
             
                # Query if extended.
         | 
| @@ -73,7 +57,11 @@ module PatchELF | |
| 73 57 | 
             
                  defined?(@threshold)
         | 
| 74 58 | 
             
                end
         | 
| 75 59 |  | 
| 60 | 
            +
                # Get correct offset after the extension.
         | 
| 61 | 
            +
                #
         | 
| 62 | 
            +
                # @param [Integer] off
         | 
| 76 63 | 
             
                # @return [Integer]
         | 
| 64 | 
            +
                #   Shifted offset.
         | 
| 77 65 | 
             
                def extended_offset(off)
         | 
| 78 66 | 
             
                  return off unless defined?(@threshold)
         | 
| 79 67 | 
             
                  return off if off < @threshold
         | 
| @@ -83,37 +71,74 @@ module PatchELF | |
| 83 71 |  | 
| 84 72 | 
             
                private
         | 
| 85 73 |  | 
| 86 | 
            -
                def  | 
| 87 | 
            -
                   | 
| 88 | 
            -
                   | 
| 89 | 
            -
             | 
| 90 | 
            -
                   | 
| 74 | 
            +
                def fgap_method
         | 
| 75 | 
            +
                  idx = find_gap { |prv, nxt| nxt.file_head - prv.file_tail }
         | 
| 76 | 
            +
                  return false if idx.nil?
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  loads = load_segments
         | 
| 79 | 
            +
                  # prefer extend backwardly
         | 
| 80 | 
            +
                  return extend_backward(loads[idx - 1]) if writable?(loads[idx - 1])
         | 
| 91 81 |  | 
| 92 | 
            -
                   | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
                   | 
| 82 | 
            +
                  extend_forward(loads[idx])
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def extend_backward(seg, size = @request_size)
         | 
| 86 | 
            +
                  invoke_callbacks(seg, seg.file_tail)
         | 
| 87 | 
            +
                  seg.header.p_filesz += size
         | 
| 88 | 
            +
                  seg.header.p_memsz += size
         | 
| 89 | 
            +
                  true
         | 
| 90 | 
            +
                end
         | 
| 97 91 |  | 
| 92 | 
            +
                def extend_forward(seg, size = @request_size)
         | 
| 93 | 
            +
                  seg.header.p_offset -= size
         | 
| 94 | 
            +
                  seg.header.p_vaddr -= size
         | 
| 95 | 
            +
                  seg.header.p_filesz += size
         | 
| 96 | 
            +
                  seg.header.p_memsz += size
         | 
| 97 | 
            +
                  invoke_callbacks(seg, seg.file_head)
         | 
| 98 98 | 
             
                  true
         | 
| 99 99 | 
             
                end
         | 
| 100 100 |  | 
| 101 | 
            -
                 | 
| 102 | 
            -
             | 
| 103 | 
            -
                  #  | 
| 104 | 
            -
                   | 
| 101 | 
            +
                def mgap_method
         | 
| 102 | 
            +
                  # |  1  | |  2  |
         | 
| 103 | 
            +
                  # |  1  |        |  2  |
         | 
| 104 | 
            +
                  #=>
         | 
| 105 | 
            +
                  # |  1      | |  2  |
         | 
| 106 | 
            +
                  # |  1      |    |  2  |
         | 
| 107 | 
            +
                  idx = find_gap(check_sz: false) { |prv, nxt| PatchELF::Helper.aligndown(nxt.mem_head) - prv.mem_tail }
         | 
| 108 | 
            +
                  return false if idx.nil?
         | 
| 105 109 |  | 
| 106 | 
            -
                  loads = load_segments | 
| 107 | 
            -
             | 
| 110 | 
            +
                  loads = load_segments
         | 
| 111 | 
            +
                  @threshold = loads[idx].file_head
         | 
| 112 | 
            +
                  @extend_size = PatchELF::Helper.alignup(@request_size)
         | 
| 113 | 
            +
                  shift_attributes
         | 
| 114 | 
            +
                  # prefer backward than forward
         | 
| 115 | 
            +
                  return extend_backward(loads[idx - 1]) if writable?(loads[idx - 1])
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  # note: loads[idx].file_head has been changed in shift_attributes
         | 
| 118 | 
            +
                  extend_forward(loads[idx], @extend_size)
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                def find_gap(check_sz: true)
         | 
| 122 | 
            +
                  loads = load_segments
         | 
| 123 | 
            +
                  loads.each_with_index do |l, i|
         | 
| 124 | 
            +
                    next if i.zero?
         | 
| 125 | 
            +
                    next unless writable?(l) || writable?(loads[i - 1])
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                    sz = yield(loads[i - 1], l)
         | 
| 128 | 
            +
                    abnormal_elf('LOAD segments are out of order.') if check_sz && sz.negative?
         | 
| 129 | 
            +
                    next unless sz >= @request_size
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                    return i
         | 
| 108 132 | 
             
                  end
         | 
| 109 | 
            -
                   | 
| 133 | 
            +
                  nil
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                def new_load_method
         | 
| 137 | 
            +
                  raise NotImplementedError
         | 
| 138 | 
            +
                end
         | 
| 110 139 |  | 
| 111 | 
            -
             | 
| 112 | 
            -
                   | 
| 113 | 
            -
                  size = if loads.size == 1 then Float::INFINITY
         | 
| 114 | 
            -
                         else loads[1].head - loads.first.tail
         | 
| 115 | 
            -
                         end
         | 
| 116 | 
            -
                  @gap_between_load = PatchELF::Interval.new(loads.first.tail, size)
         | 
| 140 | 
            +
                def writable?(seg)
         | 
| 141 | 
            +
                  seg.readable? && seg.writable?
         | 
| 117 142 | 
             
                end
         | 
| 118 143 |  | 
| 119 144 | 
             
                # For all attributes >= threshold, += offset
         | 
| @@ -123,14 +148,18 @@ module PatchELF | |
| 123 148 | 
             
                  #   all
         | 
| 124 149 | 
             
                  # Segments:
         | 
| 125 150 | 
             
                  #   all
         | 
| 126 | 
            -
                  # XXX: will be buggy if  | 
| 151 | 
            +
                  # XXX: will be buggy if someday the number of segments can be changed.
         | 
| 127 152 |  | 
| 128 153 | 
             
                  # Bottom-up
         | 
| 129 154 | 
             
                  @elf.each_sections do |sec|
         | 
| 130 155 | 
             
                    sec.header.sh_offset += extend_size if sec.header.sh_offset >= threshold
         | 
| 131 156 | 
             
                  end
         | 
| 132 157 | 
             
                  @elf.each_segments do |seg|
         | 
| 133 | 
            -
                     | 
| 158 | 
            +
                    next unless seg.header.p_offset >= threshold
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                    seg.header.p_offset += extend_size
         | 
| 161 | 
            +
                    # We have to change align of LOAD segment since ld.so checks it.
         | 
| 162 | 
            +
                    seg.header.p_align = Helper::PAGE_SIZE if seg.is_a?(ELFTools::Segments::LoadSegment)
         | 
| 134 163 | 
             
                  end
         | 
| 135 164 |  | 
| 136 165 | 
             
                  @elf.header.e_shoff += extend_size if @elf.header.e_shoff >= threshold
         | 
| @@ -140,28 +169,16 @@ module PatchELF | |
| 140 169 | 
             
                  @elf.segments_by_type(:load)
         | 
| 141 170 | 
             
                end
         | 
| 142 171 |  | 
| 143 | 
            -
                def  | 
| 144 | 
            -
                   | 
| 145 | 
            -
                  # We can assume loads.size >= 2 because
         | 
| 146 | 
            -
                  # 0: has raised an exception before
         | 
| 147 | 
            -
                  # 1: the gap must be used, nobody cares extendable size.
         | 
| 148 | 
            -
                  # Calcluate the max size of the first LOAD segment can be.
         | 
| 149 | 
            -
                  PatchELF::Helper.aligndown(loads[1].mem_head) - loads.first.mem_tail >= request_size
         | 
| 150 | 
            -
                end
         | 
| 151 | 
            -
             | 
| 152 | 
            -
                def invoke_callbacks
         | 
| 153 | 
            -
                  seg = load_segments.first
         | 
| 154 | 
            -
                  cur = gap_between_load.head
         | 
| 172 | 
            +
                def invoke_callbacks(seg, start)
         | 
| 173 | 
            +
                  cur = start
         | 
| 155 174 | 
             
                  @request.each do |sz, block|
         | 
| 156 175 | 
             
                    block.call(cur, seg.offset_to_vma(cur))
         | 
| 157 176 | 
             
                    cur += sz
         | 
| 158 177 | 
             
                  end
         | 
| 159 178 | 
             
                end
         | 
| 160 179 |  | 
| 161 | 
            -
                def  | 
| 162 | 
            -
                   | 
| 163 | 
            -
                  seg.header.p_filesz += size
         | 
| 164 | 
            -
                  seg.header.p_memsz += size
         | 
| 180 | 
            +
                def abnormal_elf(msg)
         | 
| 181 | 
            +
                  raise ArgumentError, msg
         | 
| 165 182 | 
             
                end
         | 
| 166 183 | 
             
              end
         | 
| 167 184 | 
             
            end
         | 
    
        data/lib/patchelf/patcher.rb
    CHANGED
    
    | @@ -1,8 +1,10 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 1 | 
            +
            # encoding: ascii-8bit
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'elftools/elf_file'
         | 
| 3 5 |  | 
| 4 6 | 
             
            require 'patchelf/logger'
         | 
| 5 | 
            -
            require 'patchelf/ | 
| 7 | 
            +
            require 'patchelf/saver'
         | 
| 6 8 |  | 
| 7 9 | 
             
            module PatchELF
         | 
| 8 10 | 
             
              # Class to handle all patching things.
         | 
| @@ -17,6 +19,16 @@ module PatchELF | |
| 17 19 | 
             
                  @in_file = filename
         | 
| 18 20 | 
             
                  @elf = ELFTools::ELFFile.new(File.open(filename))
         | 
| 19 21 | 
             
                  @set = {}
         | 
| 22 | 
            +
                  @rpath_sym = :runpath
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                # @return [String?]
         | 
| 26 | 
            +
                #   Get interpreter's name.
         | 
| 27 | 
            +
                # @example
         | 
| 28 | 
            +
                #   PatchELF::Patcher.new('/bin/ls').interpreter
         | 
| 29 | 
            +
                #   #=> "/lib64/ld-linux-x86-64.so.2"
         | 
| 30 | 
            +
                def interpreter
         | 
| 31 | 
            +
                  @set[:interpreter] || interpreter_
         | 
| 20 32 | 
             
                end
         | 
| 21 33 |  | 
| 22 34 | 
             
                # Set interpreter's name.
         | 
| @@ -26,11 +38,73 @@ module PatchELF | |
| 26 38 | 
             
                # @param [String] interp
         | 
| 27 39 | 
             
                # @macro note_apply
         | 
| 28 40 | 
             
                def interpreter=(interp)
         | 
| 29 | 
            -
                  return if  | 
| 41 | 
            +
                  return if interpreter_.nil? # will also show warning if there's no interp segment.
         | 
| 30 42 |  | 
| 31 43 | 
             
                  @set[:interpreter] = interp
         | 
| 32 44 | 
             
                end
         | 
| 33 45 |  | 
| 46 | 
            +
                # Get needed libraries.
         | 
| 47 | 
            +
                # @return [Array<String>]
         | 
| 48 | 
            +
                # @example
         | 
| 49 | 
            +
                #   patcher = PatchELF::Patcher.new('/bin/ls')
         | 
| 50 | 
            +
                #   patcher.needed
         | 
| 51 | 
            +
                #   #=> ["libselinux.so.1", "libc.so.6"]
         | 
| 52 | 
            +
                def needed
         | 
| 53 | 
            +
                  @set[:needed] || needed_
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                # Set needed libraries.
         | 
| 57 | 
            +
                # @param [Array<String>] needs
         | 
| 58 | 
            +
                # @macro note_apply
         | 
| 59 | 
            +
                def needed=(needs)
         | 
| 60 | 
            +
                  @set[:needed] = needs
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                # Add the needed library.
         | 
| 64 | 
            +
                # @param [String] need
         | 
| 65 | 
            +
                # @return [void]
         | 
| 66 | 
            +
                # @macro note_apply
         | 
| 67 | 
            +
                def add_needed(need)
         | 
| 68 | 
            +
                  @set[:needed] ||= needed_
         | 
| 69 | 
            +
                  @set[:needed] << need
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                # Remove the needed library.
         | 
| 73 | 
            +
                # @param [String] need
         | 
| 74 | 
            +
                # @return [void]
         | 
| 75 | 
            +
                # @macro note_apply
         | 
| 76 | 
            +
                def remove_needed(need)
         | 
| 77 | 
            +
                  @set[:needed] ||= needed_
         | 
| 78 | 
            +
                  @set[:needed].delete(need)
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                # Replace needed library +src+ with +tar+.
         | 
| 82 | 
            +
                #
         | 
| 83 | 
            +
                # @param [String] src
         | 
| 84 | 
            +
                #   Library to be replaced.
         | 
| 85 | 
            +
                # @param [String] tar
         | 
| 86 | 
            +
                #   Library replace with.
         | 
| 87 | 
            +
                # @return [void]
         | 
| 88 | 
            +
                # @macro note_apply
         | 
| 89 | 
            +
                def replace_needed(src, tar)
         | 
| 90 | 
            +
                  @set[:needed] ||= needed_
         | 
| 91 | 
            +
                  @set[:needed].map! { |v| v == src ? tar : v }
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                # Get the soname of a shared library.
         | 
| 95 | 
            +
                # @return [String?] The name.
         | 
| 96 | 
            +
                # @example
         | 
| 97 | 
            +
                #   patcher = PatchELF::Patcher.new('/bin/ls')
         | 
| 98 | 
            +
                #   patcher.soname
         | 
| 99 | 
            +
                #   # [WARN] Entry DT_SONAME not found, not a shared library?
         | 
| 100 | 
            +
                #   #=> nil
         | 
| 101 | 
            +
                # @example
         | 
| 102 | 
            +
                #   PatchELF::Patcher.new('/lib/x86_64-linux-gnu/libc.so.6').soname
         | 
| 103 | 
            +
                #   #=> "libc.so.6"
         | 
| 104 | 
            +
                def soname
         | 
| 105 | 
            +
                  @set[:soname] || soname_
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 34 108 | 
             
                # Set soname.
         | 
| 35 109 | 
             
                #
         | 
| 36 110 | 
             
                # If the input ELF is not a shared library with a soname,
         | 
| @@ -38,19 +112,32 @@ module PatchELF | |
| 38 112 | 
             
                # @param [String] name
         | 
| 39 113 | 
             
                # @macro note_apply
         | 
| 40 114 | 
             
                def soname=(name)
         | 
| 41 | 
            -
                  return if  | 
| 115 | 
            +
                  return if soname_.nil?
         | 
| 42 116 |  | 
| 43 117 | 
             
                  @set[:soname] = name
         | 
| 44 118 | 
             
                end
         | 
| 45 119 |  | 
| 46 | 
            -
                #  | 
| 120 | 
            +
                # Get runpath.
         | 
| 121 | 
            +
                # @return [String?]
         | 
| 122 | 
            +
                def runpath
         | 
| 123 | 
            +
                  @set[@rpath_sym] || runpath_
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                # Set runpath.
         | 
| 47 127 | 
             
                #
         | 
| 48 | 
            -
                # If  | 
| 49 | 
            -
                # a new  | 
| 50 | 
            -
                # @param [String]  | 
| 128 | 
            +
                # If DT_RUNPATH is not presented in the input ELF,
         | 
| 129 | 
            +
                # a new DT_RUNPATH attribute will be inserted into the DYNAMIC segment.
         | 
| 130 | 
            +
                # @param [String] runpath
         | 
| 51 131 | 
             
                # @macro note_apply
         | 
| 52 | 
            -
                def  | 
| 53 | 
            -
                  @set[ | 
| 132 | 
            +
                def runpath=(runpath)
         | 
| 133 | 
            +
                  @set[@rpath_sym] = runpath
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                # Set all operations related to DT_RUNPATH to use DT_RPATH.
         | 
| 137 | 
            +
                # @return [self]
         | 
| 138 | 
            +
                def use_rpath!
         | 
| 139 | 
            +
                  @rpath_sym = :rpath
         | 
| 140 | 
            +
                  self
         | 
| 54 141 | 
             
                end
         | 
| 55 142 |  | 
| 56 143 | 
             
                # Save the patched ELF as +out_file+.
         | 
| @@ -58,87 +145,18 @@ module PatchELF | |
| 58 145 | 
             
                #   If +out_file+ is +nil+, the original input file will be modified.
         | 
| 59 146 | 
             
                # @return [void]
         | 
| 60 147 | 
             
                def save(out_file = nil)
         | 
| 61 | 
            -
                  # TODO: Test if we can save twice, and the output files are exactly same.
         | 
| 62 148 | 
             
                  # If nothing is modified, return directly.
         | 
| 63 149 | 
             
                  return if out_file.nil? && !dirty?
         | 
| 64 150 |  | 
| 65 151 | 
             
                  out_file ||= @in_file
         | 
| 66 | 
            -
                   | 
| 67 | 
            -
                  @inline_patch = {}
         | 
| 68 | 
            -
                  @mm = PatchELF::MM.new(@elf)
         | 
| 69 | 
            -
                  # Patching interpreter is the easiest.
         | 
| 70 | 
            -
                  patch_interpreter(@set[:interpreter])
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                  @mm.dispatch!
         | 
| 73 | 
            -
             | 
| 74 | 
            -
                  FileUtils.cp(@in_file, out_file) if out_file != @in_file
         | 
| 75 | 
            -
                  # if @mm.extend_size != 0:
         | 
| 76 | 
            -
                  # 1. Remember all data after the original second LOAD
         | 
| 77 | 
            -
                  # 2. Apply patches before the second LOAD.
         | 
| 78 | 
            -
                  # 3. Apply patches located after the second LOAD.
         | 
| 79 | 
            -
             | 
| 80 | 
            -
                  File.open(out_file, 'r+') do |f|
         | 
| 81 | 
            -
                    if @mm.extended?
         | 
| 82 | 
            -
                      original_head = @mm.threshold
         | 
| 83 | 
            -
                      extra = {}
         | 
| 84 | 
            -
                      # Copy all data after the second load
         | 
| 85 | 
            -
                      @elf.stream.pos = original_head
         | 
| 86 | 
            -
                      extra[original_head + @mm.extend_size] = @elf.stream.read # read to end
         | 
| 87 | 
            -
                      # zero out the 'gap' we created
         | 
| 88 | 
            -
                      extra[original_head] = "\x00" * @mm.extend_size
         | 
| 89 | 
            -
                      extra.each do |pos, str|
         | 
| 90 | 
            -
                        f.pos = pos
         | 
| 91 | 
            -
                        f.write(str)
         | 
| 92 | 
            -
                      end
         | 
| 93 | 
            -
                    end
         | 
| 94 | 
            -
                    @elf.patches.each do |pos, str|
         | 
| 95 | 
            -
                      f.pos = @mm.extended_offset(pos)
         | 
| 96 | 
            -
                      f.write(str)
         | 
| 97 | 
            -
                    end
         | 
| 98 | 
            -
             | 
| 99 | 
            -
                    @inline_patch.each do |pos, str|
         | 
| 100 | 
            -
                      f.pos = pos
         | 
| 101 | 
            -
                      f.write(str)
         | 
| 102 | 
            -
                    end
         | 
| 103 | 
            -
                  end
         | 
| 104 | 
            -
             | 
| 105 | 
            -
                  # Let output file have the same permission as input.
         | 
| 106 | 
            -
                  FileUtils.chmod(File.stat(@in_file).mode, out_file)
         | 
| 107 | 
            -
                end
         | 
| 108 | 
            -
             | 
| 109 | 
            -
                # Get name(s) of interpreter, needed libraries, rpath, or soname.
         | 
| 110 | 
            -
                #
         | 
| 111 | 
            -
                # @param [:interpreter, :needed, :rpath, :soname] name
         | 
| 112 | 
            -
                # @return [String, Array<String>, nil]
         | 
| 113 | 
            -
                #   Returns name(s) fetched from ELF.
         | 
| 114 | 
            -
                # @example
         | 
| 115 | 
            -
                #   patcher = Patcher.new('/bin/ls')
         | 
| 116 | 
            -
                #   patcher.get(:interpreter)
         | 
| 117 | 
            -
                #   #=> "/lib64/ld-linux-x86-64.so.2"
         | 
| 118 | 
            -
                #   patcher.get(:needed)
         | 
| 119 | 
            -
                #   #=> ["libselinux.so.1", "libc.so.6"]
         | 
| 120 | 
            -
                #
         | 
| 121 | 
            -
                #   patcher.get(:soname)
         | 
| 122 | 
            -
                #   # [WARN] Entry DT_SONAME not found, not a shared library?
         | 
| 123 | 
            -
                #   #=> nil
         | 
| 124 | 
            -
                # @example
         | 
| 125 | 
            -
                #   Patcher.new('/lib/x86_64-linux-gnu/libc.so.6').get(:soname)
         | 
| 126 | 
            -
                #   #=> "libc.so.6"
         | 
| 127 | 
            -
                def get(name)
         | 
| 128 | 
            -
                  return unless %i[interpreter needed rpath soname].include?(name)
         | 
| 129 | 
            -
                  return @set[name] if @set[name]
         | 
| 152 | 
            +
                  saver = PatchELF::Saver.new(@in_file, out_file, @set)
         | 
| 130 153 |  | 
| 131 | 
            -
                   | 
| 154 | 
            +
                  saver.save!
         | 
| 132 155 | 
             
                end
         | 
| 133 156 |  | 
| 134 157 | 
             
                private
         | 
| 135 158 |  | 
| 136 | 
            -
                 | 
| 137 | 
            -
                #   Get interpreter's name.
         | 
| 138 | 
            -
                # @example
         | 
| 139 | 
            -
                #   Patcher.new('/bin/ls').interpreter
         | 
| 140 | 
            -
                #   #=> "/lib64/ld-linux-x86-64.so.2"
         | 
| 141 | 
            -
                def interpreter
         | 
| 159 | 
            +
                def interpreter_
         | 
| 142 160 | 
             
                  segment = @elf.segment_by_type(:interp)
         | 
| 143 161 | 
             
                  return PatchELF::Logger.warn('No interpreter found.') if segment.nil?
         | 
| 144 162 |  | 
| @@ -146,7 +164,7 @@ module PatchELF | |
| 146 164 | 
             
                end
         | 
| 147 165 |  | 
| 148 166 | 
             
                # @return [Array<String>]
         | 
| 149 | 
            -
                def  | 
| 167 | 
            +
                def needed_
         | 
| 150 168 | 
             
                  segment = dynamic_or_log
         | 
| 151 169 | 
             
                  return if segment.nil?
         | 
| 152 170 |  | 
| @@ -154,64 +172,20 @@ module PatchELF | |
| 154 172 | 
             
                end
         | 
| 155 173 |  | 
| 156 174 | 
             
                # @return [String?]
         | 
| 157 | 
            -
                def  | 
| 158 | 
            -
                   | 
| 159 | 
            -
                  tag_name_or_log(:rpath, 'Entry DT_RPATH not found.')
         | 
| 175 | 
            +
                def runpath_
         | 
| 176 | 
            +
                  tag_name_or_log(@rpath_sym, "Entry DT_#{@rpath_sym.to_s.upcase} not found.")
         | 
| 160 177 | 
             
                end
         | 
| 161 178 |  | 
| 162 179 | 
             
                # @return [String?]
         | 
| 163 | 
            -
                def  | 
| 180 | 
            +
                def soname_
         | 
| 164 181 | 
             
                  tag_name_or_log(:soname, 'Entry DT_SONAME not found, not a shared library?')
         | 
| 165 182 | 
             
                end
         | 
| 166 183 |  | 
| 167 | 
            -
                def patch_interpreter(new_interp)
         | 
| 168 | 
            -
                  return if new_interp.nil?
         | 
| 169 | 
            -
             | 
| 170 | 
            -
                  new_interp += "\x00"
         | 
| 171 | 
            -
                  old_interp = interpreter + "\x00"
         | 
| 172 | 
            -
                  return if old_interp == new_interp
         | 
| 173 | 
            -
             | 
| 174 | 
            -
                  # These headers must be found here but not in the proc.
         | 
| 175 | 
            -
                  seg_header = @elf.segment_by_type(:interp).header
         | 
| 176 | 
            -
                  sec_header = section_header('.interp')
         | 
| 177 | 
            -
             | 
| 178 | 
            -
                  patch = proc do |off, vaddr|
         | 
| 179 | 
            -
                    # Register an inline patching
         | 
| 180 | 
            -
                    inline_patch(off, new_interp)
         | 
| 181 | 
            -
             | 
| 182 | 
            -
                    # The patching feature of ELFTools
         | 
| 183 | 
            -
                    seg_header.p_offset = off
         | 
| 184 | 
            -
                    seg_header.p_vaddr = seg_header.p_paddr = vaddr
         | 
| 185 | 
            -
                    seg_header.p_filesz = seg_header.p_memsz = new_interp.size
         | 
| 186 | 
            -
             | 
| 187 | 
            -
                    if sec_header
         | 
| 188 | 
            -
                      sec_header.sh_offset = off
         | 
| 189 | 
            -
                      sec_header.sh_size = new_interp.size
         | 
| 190 | 
            -
                    end
         | 
| 191 | 
            -
                  end
         | 
| 192 | 
            -
             | 
| 193 | 
            -
                  if new_interp.size <= old_interp.size
         | 
| 194 | 
            -
                    # easy case
         | 
| 195 | 
            -
                    patch.call(seg_header.p_offset.to_i, seg_header.p_vaddr.to_i)
         | 
| 196 | 
            -
                  else
         | 
| 197 | 
            -
                    # hard case, we have to request a new LOAD area
         | 
| 198 | 
            -
                    @mm.malloc(new_interp.size, &patch)
         | 
| 199 | 
            -
                  end
         | 
| 200 | 
            -
                end
         | 
| 201 | 
            -
             | 
| 202 184 | 
             
                # @return [Boolean]
         | 
| 203 185 | 
             
                def dirty?
         | 
| 204 186 | 
             
                  @set.any?
         | 
| 205 187 | 
             
                end
         | 
| 206 188 |  | 
| 207 | 
            -
                # @return [ELFTools::Sections::Section?]
         | 
| 208 | 
            -
                def section_header(name)
         | 
| 209 | 
            -
                  sec = @elf.section_by_name(name)
         | 
| 210 | 
            -
                  return if sec.nil?
         | 
| 211 | 
            -
             | 
| 212 | 
            -
                  sec.header
         | 
| 213 | 
            -
                end
         | 
| 214 | 
            -
             | 
| 215 189 | 
             
                def tag_name_or_log(type, log_msg)
         | 
| 216 190 | 
             
                  segment = dynamic_or_log
         | 
| 217 191 | 
             
                  return if segment.nil?
         | 
| @@ -227,12 +201,5 @@ module PatchELF | |
| 227 201 | 
             
                    PatchELF::Logger.warn('DYNAMIC segment not found, might be a statically-linked ELF?') if s.nil?
         | 
| 228 202 | 
             
                  end
         | 
| 229 203 | 
             
                end
         | 
| 230 | 
            -
             | 
| 231 | 
            -
                # This can only be used for patching interpreter's name
         | 
| 232 | 
            -
                # or set strings in a malloc-ed area.
         | 
| 233 | 
            -
                # i.e. NEVER intend to change the string defined in strtab
         | 
| 234 | 
            -
                def inline_patch(off, str)
         | 
| 235 | 
            -
                  @inline_patch[@mm.extended_offset(off)] = str
         | 
| 236 | 
            -
                end
         | 
| 237 204 | 
             
              end
         | 
| 238 205 | 
             
            end
         | 
| @@ -0,0 +1,284 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'elftools/constants'
         | 
| 4 | 
            +
            require 'elftools/elf_file'
         | 
| 5 | 
            +
            require 'elftools/structs'
         | 
| 6 | 
            +
            require 'elftools/util'
         | 
| 7 | 
            +
            require 'fileutils'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            require 'patchelf/mm'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            module PatchELF
         | 
| 12 | 
            +
              # Internal use only.
         | 
| 13 | 
            +
              #
         | 
| 14 | 
            +
              # For {Patcher} to do patching things and save to file.
         | 
| 15 | 
            +
              # @private
         | 
| 16 | 
            +
              class Saver
         | 
| 17 | 
            +
                attr_reader :in_file # @return [String] Input filename.
         | 
| 18 | 
            +
                attr_reader :out_file # @return [String] Output filename.
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                # Instantiate a {Saver} object.
         | 
| 21 | 
            +
                # @param [String] in_file
         | 
| 22 | 
            +
                # @param [String] out_file
         | 
| 23 | 
            +
                # @param [{Symbol => String, Array}] set
         | 
| 24 | 
            +
                def initialize(in_file, out_file, set)
         | 
| 25 | 
            +
                  @in_file = in_file
         | 
| 26 | 
            +
                  @out_file = out_file
         | 
| 27 | 
            +
                  @set = set
         | 
| 28 | 
            +
                  # [{Integer => String}]
         | 
| 29 | 
            +
                  @inline_patch = {}
         | 
| 30 | 
            +
                  @elf = ELFTools::ELFFile.new(File.open(in_file))
         | 
| 31 | 
            +
                  @mm = PatchELF::MM.new(@elf)
         | 
| 32 | 
            +
                  @strtab_extend_requests = []
         | 
| 33 | 
            +
                  @append_dyn = []
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                # @return [void]
         | 
| 37 | 
            +
                def save!
         | 
| 38 | 
            +
                  # In this method we assume all attributes that should exist do exist.
         | 
| 39 | 
            +
                  # e.g. DT_INTERP, DT_DYNAMIC. These should have been checked in the patcher.
         | 
| 40 | 
            +
                  patch_interpreter
         | 
| 41 | 
            +
                  patch_dynamic
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  @mm.dispatch!
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  FileUtils.cp(in_file, out_file) if out_file != in_file
         | 
| 46 | 
            +
                  patch_out(@out_file)
         | 
| 47 | 
            +
                  # Let output file have the same permission as input.
         | 
| 48 | 
            +
                  FileUtils.chmod(File.stat(in_file).mode, out_file)
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                private
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def patch_interpreter
         | 
| 54 | 
            +
                  return if @set[:interpreter].nil?
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  new_interp = @set[:interpreter] + "\x00"
         | 
| 57 | 
            +
                  old_interp = @elf.segment_by_type(:interp).interp_name + "\x00"
         | 
| 58 | 
            +
                  return if old_interp == new_interp
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  # These headers must be found here but not in the proc.
         | 
| 61 | 
            +
                  seg_header = @elf.segment_by_type(:interp).header
         | 
| 62 | 
            +
                  sec_header = section_header('.interp')
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  patch = proc do |off, vaddr|
         | 
| 65 | 
            +
                    # Register an inline patching
         | 
| 66 | 
            +
                    inline_patch(off, new_interp)
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    # The patching feature of ELFTools
         | 
| 69 | 
            +
                    seg_header.p_offset = off
         | 
| 70 | 
            +
                    seg_header.p_vaddr = seg_header.p_paddr = vaddr
         | 
| 71 | 
            +
                    seg_header.p_filesz = seg_header.p_memsz = new_interp.size
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    if sec_header
         | 
| 74 | 
            +
                      sec_header.sh_offset = off
         | 
| 75 | 
            +
                      sec_header.sh_size = new_interp.size
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  if new_interp.size <= old_interp.size
         | 
| 80 | 
            +
                    # easy case
         | 
| 81 | 
            +
                    patch.call(seg_header.p_offset.to_i, seg_header.p_vaddr.to_i)
         | 
| 82 | 
            +
                  else
         | 
| 83 | 
            +
                    # hard case, we have to request a new LOAD area
         | 
| 84 | 
            +
                    @mm.malloc(new_interp.size, &patch)
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def patch_dynamic
         | 
| 89 | 
            +
                  # We never do inline patching on strtab's string.
         | 
| 90 | 
            +
                  # 1. Search if there's useful string exists
         | 
| 91 | 
            +
                  #   - only need header patching
         | 
| 92 | 
            +
                  # 2. Append a new string to the strtab.
         | 
| 93 | 
            +
                  #   - register strtab extension
         | 
| 94 | 
            +
                  dynamic.tags # HACK, force @tags to be defined
         | 
| 95 | 
            +
                  patch_soname if @set[:soname]
         | 
| 96 | 
            +
                  patch_runpath if @set[:runpath]
         | 
| 97 | 
            +
                  patch_runpath(:rpath) if @set[:rpath]
         | 
| 98 | 
            +
                  patch_needed if @set[:needed]
         | 
| 99 | 
            +
                  malloc_strtab!
         | 
| 100 | 
            +
                  expand_dynamic!
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                def patch_soname
         | 
| 104 | 
            +
                  # The tag must exist.
         | 
| 105 | 
            +
                  so_tag = dynamic.tag_by_type(:soname)
         | 
| 106 | 
            +
                  reg_str_table(@set[:soname]) do |idx|
         | 
| 107 | 
            +
                    so_tag.header.d_val = idx
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
                end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                def patch_runpath(sym = :runpath)
         | 
| 112 | 
            +
                  tag = dynamic.tag_by_type(sym)
         | 
| 113 | 
            +
                  tag = tag.nil? ? lazy_dyn(sym) : tag.header
         | 
| 114 | 
            +
                  reg_str_table(@set[sym]) do |idx|
         | 
| 115 | 
            +
                    tag.d_val = idx
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                # To mark a not-using tag
         | 
| 120 | 
            +
                IGNORE = ELFTools::Constants::DT_LOOS
         | 
| 121 | 
            +
                def patch_needed
         | 
| 122 | 
            +
                  original_needs = dynamic.tags_by_type(:needed)
         | 
| 123 | 
            +
                  @set[:needed].uniq!
         | 
| 124 | 
            +
                  # 3 sets:
         | 
| 125 | 
            +
                  # 1. in original and in needs - remain unchanged
         | 
| 126 | 
            +
                  # 2. in original but not in needs - remove
         | 
| 127 | 
            +
                  # 3. not in original and in needs - append
         | 
| 128 | 
            +
                  original_needs.each do |n|
         | 
| 129 | 
            +
                    next if @set[:needed].include?(n.name)
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                    n.header.d_tag = IGNORE # temporarily mark
         | 
| 132 | 
            +
                  end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                  extra = @set[:needed] - original_needs.map(&:name)
         | 
| 135 | 
            +
                  original_needs.each do |n|
         | 
| 136 | 
            +
                    break if extra.empty?
         | 
| 137 | 
            +
                    next if n.header.d_tag != IGNORE
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                    n.header.d_tag = ELFTools::Constants::DT_NEEDED
         | 
| 140 | 
            +
                    reg_str_table(extra.shift) { |idx| n.header.d_val = idx }
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
                  return if extra.empty?
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                  # no spaces, need append
         | 
| 145 | 
            +
                  extra.each do |name|
         | 
| 146 | 
            +
                    tag = lazy_dyn(:needed)
         | 
| 147 | 
            +
                    reg_str_table(name) { |idx| tag.d_val = idx }
         | 
| 148 | 
            +
                  end
         | 
| 149 | 
            +
                end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                # Create a temp tag header.
         | 
| 152 | 
            +
                # @return [ELFTools::Structs::ELF_Dyn]
         | 
| 153 | 
            +
                def lazy_dyn(sym)
         | 
| 154 | 
            +
                  ELFTools::Structs::ELF_Dyn.new(endian: @elf.endian).tap do |dyn|
         | 
| 155 | 
            +
                    @append_dyn << dyn
         | 
| 156 | 
            +
                    dyn.elf_class = @elf.elf_class
         | 
| 157 | 
            +
                    dyn.d_tag = ELFTools::Util.to_constant(ELFTools::Constants::DT, sym)
         | 
| 158 | 
            +
                  end
         | 
| 159 | 
            +
                end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                def expand_dynamic!
         | 
| 162 | 
            +
                  return if @append_dyn.empty?
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  dyn_sec = section_header('.dynamic')
         | 
| 165 | 
            +
                  total = dynamic.tags.map(&:header)
         | 
| 166 | 
            +
                  # the last must be a null-tag
         | 
| 167 | 
            +
                  total = total[0..-2] + @append_dyn + [total.last]
         | 
| 168 | 
            +
                  bytes = total.first.num_bytes * total.size
         | 
| 169 | 
            +
                  @mm.malloc(bytes) do |off, vaddr|
         | 
| 170 | 
            +
                    inline_patch(off, total.map(&:to_binary_s).join)
         | 
| 171 | 
            +
                    dynamic.header.p_offset = off
         | 
| 172 | 
            +
                    dynamic.header.p_vaddr = dynamic.header.p_paddr = vaddr
         | 
| 173 | 
            +
                    dynamic.header.p_filesz = dynamic.header.p_memsz = bytes
         | 
| 174 | 
            +
                    if dyn_sec
         | 
| 175 | 
            +
                      dyn_sec.sh_offset = off
         | 
| 176 | 
            +
                      dyn_sec.sh_addr = vaddr
         | 
| 177 | 
            +
                      dyn_sec.sh_size = bytes
         | 
| 178 | 
            +
                    end
         | 
| 179 | 
            +
                  end
         | 
| 180 | 
            +
                end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                def malloc_strtab!
         | 
| 183 | 
            +
                  return if @strtab_extend_requests.empty?
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                  strtab = dynamic.tag_by_type(:strtab)
         | 
| 186 | 
            +
                  # Process registered requests
         | 
| 187 | 
            +
                  need_size = strtab_string.size + @strtab_extend_requests.reduce(0) { |sum, (str, _)| sum + str.size + 1 }
         | 
| 188 | 
            +
                  dynstr = section_header('.dynstr')
         | 
| 189 | 
            +
                  @mm.malloc(need_size) do |off, vaddr|
         | 
| 190 | 
            +
                    new_str = strtab_string + @strtab_extend_requests.map(&:first).join("\x00") + "\x00"
         | 
| 191 | 
            +
                    inline_patch(off, new_str)
         | 
| 192 | 
            +
                    @strtab_extend_requests.each do |str, block|
         | 
| 193 | 
            +
                      # TODO: make here more efficient
         | 
| 194 | 
            +
                      block.call(new_str.index(str + "\x00"))
         | 
| 195 | 
            +
                    end
         | 
| 196 | 
            +
                    # Now patching strtab header
         | 
| 197 | 
            +
                    strtab.header.d_val = vaddr
         | 
| 198 | 
            +
                    # We also need to patch dynstr to let readelf have correct output.
         | 
| 199 | 
            +
                    if dynstr
         | 
| 200 | 
            +
                      dynstr.sh_size = new_str.size
         | 
| 201 | 
            +
                      dynstr.sh_offset = off
         | 
| 202 | 
            +
                      dynstr.sh_addr = vaddr
         | 
| 203 | 
            +
                    end
         | 
| 204 | 
            +
                  end
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                # @param [String] str
         | 
| 208 | 
            +
                # @yieldparam [Integer] idx
         | 
| 209 | 
            +
                # @yieldreturn [void]
         | 
| 210 | 
            +
                def reg_str_table(str, &block)
         | 
| 211 | 
            +
                  idx = strtab_string.index(str + "\x00")
         | 
| 212 | 
            +
                  # Request string is already exist
         | 
| 213 | 
            +
                  return yield idx if idx
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                  # Record the request
         | 
| 216 | 
            +
                  @strtab_extend_requests << [str, block]
         | 
| 217 | 
            +
                end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                def strtab_string
         | 
| 220 | 
            +
                  return @strtab_string if defined?(@strtab_string)
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                  # TODO: handle no strtab exists..
         | 
| 223 | 
            +
                  offset = @elf.offset_from_vma(dynamic.tag_by_type(:strtab).value)
         | 
| 224 | 
            +
                  # This is a little tricky since no length information is stored in the tag.
         | 
| 225 | 
            +
                  # We first get the file offset of the string then 'guess' where the end is.
         | 
| 226 | 
            +
                  @elf.stream.pos = offset
         | 
| 227 | 
            +
                  @strtab_string = +''
         | 
| 228 | 
            +
                  loop do
         | 
| 229 | 
            +
                    c = @elf.stream.read(1)
         | 
| 230 | 
            +
                    break unless c =~ /\x00|[[:print:]]/
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                    @strtab_string << c
         | 
| 233 | 
            +
                  end
         | 
| 234 | 
            +
                  @strtab_string
         | 
| 235 | 
            +
                end
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                # This can only be used for patching interpreter's name
         | 
| 238 | 
            +
                # or set strings in a malloc-ed area.
         | 
| 239 | 
            +
                # i.e. NEVER intend to change the string defined in strtab
         | 
| 240 | 
            +
                def inline_patch(off, str)
         | 
| 241 | 
            +
                  @inline_patch[off] = str
         | 
| 242 | 
            +
                end
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                # Modify the out_file according to registered patches.
         | 
| 245 | 
            +
                def patch_out(out_file)
         | 
| 246 | 
            +
                  File.open(out_file, 'r+') do |f|
         | 
| 247 | 
            +
                    if @mm.extended?
         | 
| 248 | 
            +
                      original_head = @mm.threshold
         | 
| 249 | 
            +
                      extra = {}
         | 
| 250 | 
            +
                      # Copy all data after the second load
         | 
| 251 | 
            +
                      @elf.stream.pos = original_head
         | 
| 252 | 
            +
                      extra[original_head + @mm.extend_size] = @elf.stream.read # read to end
         | 
| 253 | 
            +
                      # zero out the 'gap' we created
         | 
| 254 | 
            +
                      extra[original_head] = "\x00" * @mm.extend_size
         | 
| 255 | 
            +
                      extra.each do |pos, str|
         | 
| 256 | 
            +
                        f.pos = pos
         | 
| 257 | 
            +
                        f.write(str)
         | 
| 258 | 
            +
                      end
         | 
| 259 | 
            +
                    end
         | 
| 260 | 
            +
                    @elf.patches.each do |pos, str|
         | 
| 261 | 
            +
                      f.pos = @mm.extended_offset(pos)
         | 
| 262 | 
            +
                      f.write(str)
         | 
| 263 | 
            +
                    end
         | 
| 264 | 
            +
             | 
| 265 | 
            +
                    @inline_patch.each do |pos, str|
         | 
| 266 | 
            +
                      f.pos = pos
         | 
| 267 | 
            +
                      f.write(str)
         | 
| 268 | 
            +
                    end
         | 
| 269 | 
            +
                  end
         | 
| 270 | 
            +
                end
         | 
| 271 | 
            +
             | 
| 272 | 
            +
                # @return [ELFTools::Sections::Section?]
         | 
| 273 | 
            +
                def section_header(name)
         | 
| 274 | 
            +
                  sec = @elf.section_by_name(name)
         | 
| 275 | 
            +
                  return if sec.nil?
         | 
| 276 | 
            +
             | 
| 277 | 
            +
                  sec.header
         | 
| 278 | 
            +
                end
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                def dynamic
         | 
| 281 | 
            +
                  @dynamic ||= @elf.segment_by_type(:dynamic)
         | 
| 282 | 
            +
                end
         | 
| 283 | 
            +
              end
         | 
| 284 | 
            +
            end
         | 
    
        data/lib/patchelf/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: patchelf
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - david942j
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2019-01- | 
| 11 | 
            +
            date: 2019-01-15 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: elftools
         | 
| @@ -123,10 +123,10 @@ files: | |
| 123 123 | 
             
            - lib/patchelf.rb
         | 
| 124 124 | 
             
            - lib/patchelf/cli.rb
         | 
| 125 125 | 
             
            - lib/patchelf/helper.rb
         | 
| 126 | 
            -
            - lib/patchelf/interval.rb
         | 
| 127 126 | 
             
            - lib/patchelf/logger.rb
         | 
| 128 127 | 
             
            - lib/patchelf/mm.rb
         | 
| 129 128 | 
             
            - lib/patchelf/patcher.rb
         | 
| 129 | 
            +
            - lib/patchelf/saver.rb
         | 
| 130 130 | 
             
            - lib/patchelf/version.rb
         | 
| 131 131 | 
             
            homepage: https://github.com/david942j/patchelf.rb
         | 
| 132 132 | 
             
            licenses:
         | 
| @@ -147,8 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 147 147 | 
             
                - !ruby/object:Gem::Version
         | 
| 148 148 | 
             
                  version: '0'
         | 
| 149 149 | 
             
            requirements: []
         | 
| 150 | 
            -
             | 
| 151 | 
            -
            rubygems_version: 2.7.6
         | 
| 150 | 
            +
            rubygems_version: 3.0.2
         | 
| 152 151 | 
             
            signing_key: 
         | 
| 153 152 | 
             
            specification_version: 4
         | 
| 154 153 | 
             
            summary: patchelf
         | 
    
        data/lib/patchelf/interval.rb
    DELETED
    
    | @@ -1,30 +0,0 @@ | |
| 1 | 
            -
            module PatchELF
         | 
| 2 | 
            -
              # Provides easier-to-use methods for manipulating LOAD segment.
         | 
| 3 | 
            -
              #
         | 
| 4 | 
            -
              # Internal use only.
         | 
| 5 | 
            -
              class Interval
         | 
| 6 | 
            -
                include Comparable
         | 
| 7 | 
            -
             | 
| 8 | 
            -
                attr_reader :head # @return [Integer] Head.
         | 
| 9 | 
            -
                attr_reader :size # @return [Integer] Length.
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                # @param [Integer] head
         | 
| 12 | 
            -
                # @param [Integer] size
         | 
| 13 | 
            -
                def initialize(head, size)
         | 
| 14 | 
            -
                  @head = head
         | 
| 15 | 
            -
                  @size = size
         | 
| 16 | 
            -
                end
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                # Comparator.
         | 
| 19 | 
            -
                # @param [Interval] other
         | 
| 20 | 
            -
                def <=>(other)
         | 
| 21 | 
            -
                  head <=> other.head
         | 
| 22 | 
            -
                end
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                # @return [Integer]
         | 
| 25 | 
            -
                #   The end of this interval.
         | 
| 26 | 
            -
                def tail
         | 
| 27 | 
            -
                  head + size
         | 
| 28 | 
            -
                end
         | 
| 29 | 
            -
              end
         | 
| 30 | 
            -
            end
         |