mirror of
https://github.com/anchore/syft.git
synced 2026-10-11 21:57:22 +02:00
fix(golang): account for UPX's loader padding when unpacking Go binaries (#5347)
UPX pads its output to a 4 byte boundary before it writes the loader stub, and l_lsize counts from that boundary. The block chain resumed at the end of the last PT_LOAD extent plus l_lsize, so whenever the compressed extents ended off a 4 byte boundary it landed 1 to 3 bytes early, read a misaligned b_info, and stopped. The padding between the segments was never filled in, the reconstruction ended at that first hole, and .go.buildinfo in the data segment went with it, so the binary contributed no packages. The loader skip now rounds up to 4 before adding l_lsize, which matches the funpad4 in UPX's own unpacker. The crafted fixture for the tail extents put its stub straight after the head extent, the layout the bug assumed, so it now pads the way UPX does. Signed-off-by: huuyafwww <huuya.yamauchi@3-shake.com>
This commit is contained in:
@@ -502,13 +502,14 @@ type loaderSkip struct {
|
||||
}
|
||||
|
||||
// past reports the offset the chain resumes at, and false once the jump has been spent or there is no
|
||||
// stub to jump over.
|
||||
// stub to jump over. UPX pads to a 4 byte boundary before it writes the stub, and l_lsize counts from that
|
||||
// boundary rather than from where the last extent ended.
|
||||
func (l *loaderSkip) past(offset int64) (int64, bool) {
|
||||
if l.done || l.size == 0 {
|
||||
return offset, false
|
||||
}
|
||||
l.done = true
|
||||
return offset + int64(l.size), true
|
||||
return (offset+3)&^3 + int64(l.size), true
|
||||
}
|
||||
|
||||
// decompressUPXBlocks walks the b_info chain, decoding each block into its place in the reconstructed
|
||||
|
||||
@@ -718,6 +718,9 @@ func TestDecompressUPX_TailExtentsArePlacedPastTheLoader(t *testing.T) {
|
||||
// front of it. Every other crafted fixture here has l_lsize == 0, so without this skipLoader was only
|
||||
// reachable through the Docker-backed fixture.
|
||||
//
|
||||
// UPX pads to a 4 byte boundary before it writes the stub and l_lsize counts from there, so the head
|
||||
// extent here deliberately ends off that boundary: its end plus l_lsize lands inside the stub.
|
||||
//
|
||||
// note: block 1 here is not an ELF, so there are no PT_LOAD offsets and the tail lands sequentially.
|
||||
// The hole-filling path this feeds in production is covered by TestFirstHole and by the sparse
|
||||
// fixtures in upx_bounds_regression_test.go.
|
||||
@@ -727,6 +730,8 @@ func TestDecompressUPX_TailExtentsArePlacedPastTheLoader(t *testing.T) {
|
||||
|
||||
data := buildUPXHeaderWithLoader(4096, 4096, loaderSize)
|
||||
data = append(data, blockFor(t, head)...)
|
||||
require.NotZero(t, len(data)%4, "the head extent has to end off a 4 byte boundary for the padding to exist")
|
||||
data = append(data, make([]byte, 4-len(data)%4)...) // the padding UPX writes in front of the stub
|
||||
data = append(data, make([]byte, 12)...) // end marker: closes the first run of extents
|
||||
data = append(data, make([]byte, loaderSize-12)...) // the loader stub the chain skips over
|
||||
data = append(data, blockFor(t, tail)...)
|
||||
|
||||
Reference in New Issue
Block a user