google / bloaty

Bloaty: a size profiler for binaries
Apache License 2.0
4.66k stars 337 forks source link

bloaty crashes on ELF files with a 0-sized segment at an offset greater than the ELF file size #378

Open kjteske opened 1 month ago

kjteske commented 1 month ago

bloaty errors processing an ELF file where a 0-sized segment is not backed by the ELF file's contents:

$ bloaty <binary>
bloaty: region out-of-bounds  

This happens when bloaty is iterating segments and gets to the "TLS" segment in this particular file. readelf shows the .tbss section / TLS segment is at address 0x00690000, with MemSiz of 4 bytes but FileSiz is 0 bytes. With some irrelevant details redacted:

$ readelf -l -S <binary>
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
   ...
  [20] .tbss             NOBITS          00690000 680000 000004 00 WAT  0   0  4
   ...

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  EXIDX          0x648a04 0x00658a04 0x00658a04 0x36da8 0x36da8 R   0x4
  ...
  TLS            0x680000 0x00690000 0x00690000 0x00000 0x00004 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10

 Section to Segment mapping:
  Segment Sections...
   00     .ARM.exidx 
   ...
   08     .tbss 
   09  

Since FileSiz is 0 bytes, it doesn't need to be backed by the file. And we see the file indeed is smaller than the 0x00690000 offset:

$ printf "0x%x\n" $(stat --format "%s" <binary>)
0x68aaf0

The crash is within ElfFile::ReadSegment() when header->p_filesz is 0. When p_filesz is 0, we should skip calling GetRegion().

haberman commented 3 weeks ago

Sorry for the slow reply.

I'm not quite understanding why this made Bloaty crash. Your report seems to be stating that p_offset is 0x00690000, but from my reading of your readelf output, p_offset=0x680000, which is less than your file size 0x68aaf0.

I guess your PR https://github.com/google/bloaty/pull/379 should be harmless: if p_filesz is 0, then the file region is empty anyway, and there's no benefit in crashing because the offset was bad. We could even consider just changing StrictSubstr() to immediately return an empty string_view if the size is zero.

kjteske commented 3 weeks ago

I'm not quite understanding why this made Bloaty crash.

Sorry - bloaty isn't actually crashing. But it bails out on the error and exits main() , printing just the error and nothing else useful.

Your report seems to be stating that p_offset is 0x00690000, but from my reading of your readelf output, p_offset=0x680000, which is less than your file size 0x68aaf0.

Oops - yes, I didn't report this correctly. It's not the .tbss section / TLS segment that triggers the error; it's the .fusion section in a LOAD segment where we get p_offset = 0x00690000.

$ readelf -l -S <binary>
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
   ...
  [33] .fusion           NOBITS          20000000 690000 8010000 00  WA  0   0  1
   ...

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  EXIDX          0x648a04 0x00658a04 0x00658a04 0x36da8 0x36da8 R   0x4
  ...
  LOAD           0x680000 0x00690000 0x00690000 0x0a3d4 0x18e8c RW  0x10000
  ...

 Section to Segment mapping:
  Segment Sections...
   00     .ARM.exidx 
   ...
   05     .fusion 
   09  

We could even consider just changing StrictSubstr() to immediately return an empty string_view if the size is zero.

Happy to implement this if you want. My thinking, as I'm not familiar with the code and all use cases and callers of StrictSubstr(), is that there could be some use cases where we do want StrictSubstr() to throw an error here. I was only confident enough in the ReadSegment() use case here to just fix that one spot.

haberman commented 3 weeks ago

Do you know what the .fusion section is? I'd be curious to know why its offset is out-of-bounds compared to the file.

kjteske commented 3 weeks ago

Realized I showed the wrong LOAD segment in my last comment. This is the .fusion segment, note FileSiz is zero, with a large MemSiz.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x690000 0x20000000 0x20000000 0x00000 0x8010000 RW  0x10000

.fusion is something we add via a linker script to reserve a range of memory in the virtual address space right away when the program is loaded, before anything else can run and potentially take up this address range. It's ugly but necessary for a 3rdParty library/driver we use.