Skip to content

Commit 656e01b

Browse files
committed
debug/elf: use saferio.InBounds to prevent offset overflow
When applying relocations, a malformed ELF file can provide an offset that, when added to the relocation size, overflows. This wrapped-around value could then incorrectly pass the bounds check, leading to a panic when the slice is accessed with the original large offset. This change replaces the manual bounds and overflow checks in the applyRelocations* functions with calls to saferio.{InBounds32,InBounds64}. These helper functions centralize the logic for validating slice access, correctly handling both out-of-bounds and integer overflow conditions. This simplifies the relocation code and improves robustness when parsing untrusted ELF files. Fixes #75516 Change-Id: I3a1662398a981977d6cbacfa47c40707ddd87b37
1 parent 3cf1aaf commit 656e01b

File tree

3 files changed

+96
-20
lines changed

3 files changed

+96
-20
lines changed

src/debug/elf/file.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -830,13 +830,13 @@ func (f *File) applyRelocationsAMD64(dst []byte, rels []byte) error {
830830

831831
switch t {
832832
case R_X86_64_64:
833-
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
833+
if !saferio.InBounds64(dst, rela.Off, 8) || rela.Addend < 0 {
834834
continue
835835
}
836836
val64 := sym.Value + uint64(rela.Addend)
837837
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
838838
case R_X86_64_32:
839-
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
839+
if !saferio.InBounds64(dst, rela.Off, 4) || rela.Addend < 0 {
840840
continue
841841
}
842842
val32 := uint32(sym.Value) + uint32(rela.Addend)
@@ -872,7 +872,7 @@ func (f *File) applyRelocations386(dst []byte, rels []byte) error {
872872
sym := &symbols[symNo-1]
873873

874874
if t == R_386_32 {
875-
if rel.Off+4 >= uint32(len(dst)) {
875+
if !saferio.InBounds32(dst, rel.Off, 4) {
876876
continue
877877
}
878878
val := f.ByteOrder.Uint32(dst[rel.Off : rel.Off+4])
@@ -910,7 +910,7 @@ func (f *File) applyRelocationsARM(dst []byte, rels []byte) error {
910910

911911
switch t {
912912
case R_ARM_ABS32:
913-
if rel.Off+4 >= uint32(len(dst)) {
913+
if !saferio.InBounds32(dst, rel.Off, 4) {
914914
continue
915915
}
916916
val := f.ByteOrder.Uint32(dst[rel.Off : rel.Off+4])
@@ -955,13 +955,13 @@ func (f *File) applyRelocationsARM64(dst []byte, rels []byte) error {
955955

956956
switch t {
957957
case R_AARCH64_ABS64:
958-
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
958+
if !saferio.InBounds64(dst, rela.Off, 8) || rela.Addend < 0 {
959959
continue
960960
}
961961
val64 := sym.Value + uint64(rela.Addend)
962962
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
963963
case R_AARCH64_ABS32:
964-
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
964+
if !saferio.InBounds64(dst, rela.Off, 4) || rela.Addend < 0 {
965965
continue
966966
}
967967
val32 := uint32(sym.Value) + uint32(rela.Addend)
@@ -1001,7 +1001,7 @@ func (f *File) applyRelocationsPPC(dst []byte, rels []byte) error {
10011001

10021002
switch t {
10031003
case R_PPC_ADDR32:
1004-
if rela.Off+4 >= uint32(len(dst)) || rela.Addend < 0 {
1004+
if !saferio.InBounds32(dst, rela.Off, 4) || rela.Addend < 0 {
10051005
continue
10061006
}
10071007
val32 := uint32(sym.Value) + uint32(rela.Addend)
@@ -1041,13 +1041,13 @@ func (f *File) applyRelocationsPPC64(dst []byte, rels []byte) error {
10411041

10421042
switch t {
10431043
case R_PPC64_ADDR64:
1044-
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
1044+
if !saferio.InBounds64(dst, rela.Off, 8) || rela.Addend < 0 {
10451045
continue
10461046
}
10471047
val64 := sym.Value + uint64(rela.Addend)
10481048
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
10491049
case R_PPC64_ADDR32:
1050-
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
1050+
if !saferio.InBounds64(dst, rela.Off, 4) || rela.Addend < 0 {
10511051
continue
10521052
}
10531053
val32 := uint32(sym.Value) + uint32(rela.Addend)
@@ -1084,7 +1084,7 @@ func (f *File) applyRelocationsMIPS(dst []byte, rels []byte) error {
10841084

10851085
switch t {
10861086
case R_MIPS_32:
1087-
if rel.Off+4 >= uint32(len(dst)) {
1087+
if !saferio.InBounds32(dst, rel.Off, 4) {
10881088
continue
10891089
}
10901090
val := f.ByteOrder.Uint32(dst[rel.Off : rel.Off+4])
@@ -1132,13 +1132,13 @@ func (f *File) applyRelocationsMIPS64(dst []byte, rels []byte) error {
11321132

11331133
switch t {
11341134
case R_MIPS_64:
1135-
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
1135+
if !saferio.InBounds64(dst, rela.Off, 8) || rela.Addend < 0 {
11361136
continue
11371137
}
11381138
val64 := sym.Value + uint64(rela.Addend)
11391139
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
11401140
case R_MIPS_32:
1141-
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
1141+
if !saferio.InBounds64(dst, rela.Off, 4) || rela.Addend < 0 {
11421142
continue
11431143
}
11441144
val32 := uint32(sym.Value) + uint32(rela.Addend)
@@ -1180,13 +1180,13 @@ func (f *File) applyRelocationsLOONG64(dst []byte, rels []byte) error {
11801180

11811181
switch t {
11821182
case R_LARCH_64:
1183-
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
1183+
if !saferio.InBounds64(dst, rela.Off, 8) || rela.Addend < 0 {
11841184
continue
11851185
}
11861186
val64 := sym.Value + uint64(rela.Addend)
11871187
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
11881188
case R_LARCH_32:
1189-
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
1189+
if !saferio.InBounds64(dst, rela.Off, 4) || rela.Addend < 0 {
11901190
continue
11911191
}
11921192
val32 := uint32(sym.Value) + uint32(rela.Addend)
@@ -1226,13 +1226,13 @@ func (f *File) applyRelocationsRISCV64(dst []byte, rels []byte) error {
12261226

12271227
switch t {
12281228
case R_RISCV_64:
1229-
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
1229+
if !saferio.InBounds64(dst, rela.Off, 8) || rela.Addend < 0 {
12301230
continue
12311231
}
12321232
val64 := sym.Value + uint64(rela.Addend)
12331233
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
12341234
case R_RISCV_32:
1235-
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
1235+
if !saferio.InBounds64(dst, rela.Off, 4) || rela.Addend < 0 {
12361236
continue
12371237
}
12381238
val32 := uint32(sym.Value) + uint32(rela.Addend)
@@ -1272,13 +1272,13 @@ func (f *File) applyRelocationss390x(dst []byte, rels []byte) error {
12721272

12731273
switch t {
12741274
case R_390_64:
1275-
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
1275+
if !saferio.InBounds64(dst, rela.Off, 8) || rela.Addend < 0 {
12761276
continue
12771277
}
12781278
val64 := sym.Value + uint64(rela.Addend)
12791279
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
12801280
case R_390_32:
1281-
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
1281+
if !saferio.InBounds64(dst, rela.Off, 4) || rela.Addend < 0 {
12821282
continue
12831283
}
12841284
val32 := uint32(sym.Value) + uint32(rela.Addend)
@@ -1318,13 +1318,13 @@ func (f *File) applyRelocationsSPARC64(dst []byte, rels []byte) error {
13181318

13191319
switch t {
13201320
case R_SPARC_64, R_SPARC_UA64:
1321-
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
1321+
if !saferio.InBounds64(dst, rela.Off, 8) || rela.Addend < 0 {
13221322
continue
13231323
}
13241324
val64 := sym.Value + uint64(rela.Addend)
13251325
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
13261326
case R_SPARC_32, R_SPARC_UA32:
1327-
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
1327+
if !saferio.InBounds64(dst, rela.Off, 4) || rela.Addend < 0 {
13281328
continue
13291329
}
13301330
val32 := uint32(sym.Value) + uint32(rela.Addend)

src/internal/saferio/io.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package saferio
1111

1212
import (
1313
"io"
14+
"math"
1415
"unsafe"
1516
)
1617

@@ -130,3 +131,21 @@ func SliceCap[E any](c uint64) int {
130131
size := uint64(unsafe.Sizeof(v))
131132
return SliceCapWithSize(size, c)
132133
}
134+
135+
// InBounds32 reports whether S[start : start+length] can be taken
136+
// without panicking.
137+
func InBounds32[S ~[]E, E any](slice S, start, length uint32) bool {
138+
if start+length >= uint32(len(slice)) || math.MaxUint32-start < length {
139+
return false
140+
}
141+
return true
142+
}
143+
144+
// InBounds64 reports whether S[start : start+length] can be taken
145+
// without panicking.
146+
func InBounds64[S ~[]E, E any](slice S, start, length uint64) bool {
147+
if start+length >= uint64(len(slice)) || math.MaxUint64-start < length {
148+
return false
149+
}
150+
return true
151+
}

src/internal/saferio/io_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package saferio
77
import (
88
"bytes"
99
"io"
10+
"math"
1011
"testing"
1112
)
1213

@@ -134,3 +135,59 @@ func TestSliceCap(t *testing.T) {
134135
}
135136
})
136137
}
138+
func TestInBounds32(t *testing.T) {
139+
tests := []struct {
140+
name string
141+
slice []byte
142+
start uint32
143+
length uint32
144+
want bool
145+
}{
146+
{"valid range", []byte{1, 2, 3, 4, 5}, 1, 3, true},
147+
{"start+length equals len", []byte{1, 2, 3}, 0, 3, false},
148+
{"start+length exceeds len", []byte{1, 2, 3}, 2, 2, false},
149+
{"start at end", []byte{1, 2, 3}, 3, 0, false},
150+
{"zero length", []byte{1, 2, 3}, 1, 0, true},
151+
{"empty slice", []byte{}, 0, 0, false},
152+
{"maxuint32 overflow", []byte{1, 2, 3}, math.MaxUint32, 1, false},
153+
{"maxuint32 no overflow", []byte{1, 2, 3}, 0, math.MaxUint32, false},
154+
{"maxuint32 edge", []byte{1, 2, 3}, math.MaxUint32 - 1, 1, false},
155+
}
156+
157+
for _, tt := range tests {
158+
t.Run(tt.name, func(t *testing.T) {
159+
got := InBounds32(tt.slice, tt.start, tt.length)
160+
if got != tt.want {
161+
t.Errorf("InBounds32(%v, %d, %d) = %v, want %v", tt.slice, tt.start, tt.length, got, tt.want)
162+
}
163+
})
164+
}
165+
}
166+
func TestInBounds64(t *testing.T) {
167+
tests := []struct {
168+
name string
169+
slice []byte
170+
start uint64
171+
length uint64
172+
want bool
173+
}{
174+
{"valid range", []byte{1, 2, 3, 4, 5}, 1, 3, true},
175+
{"start+length equals len", []byte{1, 2, 3}, 0, 3, false},
176+
{"start+length exceeds len", []byte{1, 2, 3}, 2, 2, false},
177+
{"start at end", []byte{1, 2, 3}, 3, 0, false},
178+
{"zero length", []byte{1, 2, 3}, 1, 0, true},
179+
{"empty slice", []byte{}, 0, 0, false},
180+
{"maxuint64 overflow", []byte{1, 2, 3}, math.MaxUint64, 1, false},
181+
{"maxuint64 no overflow", []byte{1, 2, 3}, 0, math.MaxUint64, false},
182+
{"maxuint64 edge", []byte{1, 2, 3}, math.MaxUint64 - 1, 1, false},
183+
}
184+
185+
for _, tt := range tests {
186+
t.Run(tt.name, func(t *testing.T) {
187+
got := InBounds64(tt.slice, tt.start, tt.length)
188+
if got != tt.want {
189+
t.Errorf("InBounds64(%v, %d, %d) = %v, want %v", tt.slice, tt.start, tt.length, got, tt.want)
190+
}
191+
})
192+
}
193+
}

0 commit comments

Comments
 (0)