Skip to content

Commit a1f33a9

Browse files
authored
fix(elf)!: return errors from IsElfFile (#5296)
IsElfFile silently swallowed open/read errors, making it impossible for callers to distinguish "not ELF" from "could not check." Change the signature to (bool, error) so callers can act on permission, I/O, or missing-file failures. - Use io.ReadFull to correctly handle empty and short files as (false, nil) instead of leaking io.EOF as an error. - Extract hasElfMagic([4]byte) so callers with a known-size buffer skip the redundant length guard; HasElfMagic([]byte) delegates to it after the bounds check. - Replace opaque decimal magic constants with 0x7F,'E','L','F'. - Add tests for permission-denied, short-file, and empty-file error contract. BREAKING CHANGE: IsElfFile returns (bool, error) instead of bool.
1 parent b34335a commit a1f33a9

2 files changed

Lines changed: 72 additions & 20 deletions

File tree

common/elf/analyzer.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,20 @@ func Is32BitMachine(machine elf.Machine) bool {
9696
}
9797
}
9898

99+
func hasElfMagic(magic [4]byte) bool {
100+
return magic[0] == 0x7F &&
101+
magic[1] == 'E' &&
102+
magic[2] == 'L' &&
103+
magic[3] == 'F'
104+
}
105+
99106
// HasElfMagic checks if the given bytes start with the ELF magic number (0x7F 'ELF').
100107
// This is a fast check that only validates the first 4 bytes.
101108
func HasElfMagic(bytesArray []byte) bool {
102-
if len(bytesArray) >= 4 {
103-
if bytesArray[0] == 127 && bytesArray[1] == 69 && bytesArray[2] == 76 && bytesArray[3] == 70 {
104-
return true
105-
}
109+
if len(bytesArray) < 4 {
110+
return false
106111
}
107-
return false
112+
return hasElfMagic([4]byte(bytesArray[:4]))
108113
}
109114

110115
// IsElf checks if the given bytes represent a valid ELF file.
@@ -114,11 +119,13 @@ func IsElf(bytesArray []byte) bool {
114119
return HasElfMagic(bytesArray)
115120
}
116121

117-
// IsElfFile checks if the file at the given path is an ELF file (fast magic-only check).
118-
func IsElfFile(filePath string) bool {
122+
// IsElfFile performs a cheap 4-byte magic check on the file at path.
123+
// It returns (true, nil) for ELF files, (false, nil) for non-ELF files that
124+
// were readable, and (false, err) when the file could not be opened or read.
125+
func IsElfFile(filePath string) (bool, error) {
119126
file, err := os.Open(filePath)
120127
if err != nil {
121-
return false
128+
return false, err
122129
}
123130
// Suppress the default readahead window (commonly 128 KiB, see /sys/block/*/queue/read_ahead_kb)
124131
// since we only read the 4-byte ELF magic; this avoids pulling unneeded pages into the
@@ -132,13 +139,16 @@ func IsElfFile(filePath string) bool {
132139
}
133140
}()
134141

135-
magic := make([]byte, 4)
136-
n, err := file.Read(magic)
137-
if err != nil || n < 4 {
138-
return false
142+
var magic [4]byte
143+
_, err = io.ReadFull(file, magic[:])
144+
if err == io.EOF || err == io.ErrUnexpectedEOF {
145+
return false, nil
146+
}
147+
if err != nil {
148+
return false, err
139149
}
140150

141-
return HasElfMagic(magic)
151+
return hasElfMagic(magic), nil
142152
}
143153

144154
func NewElfAnalyzer(filePath string, wantedSymbols []WantedSymbol) (*ElfAnalyzer, error) {

common/elf/analyzer_test.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package elf
22

33
import (
44
"debug/elf"
5+
"errors"
56
"os"
7+
"path/filepath"
68
"strings"
79
"testing"
810

@@ -252,10 +254,12 @@ func TestIsElfFile(t *testing.T) {
252254
if elfPath == "" {
253255
t.Skip("No ELF binary found for testing")
254256
}
255-
assert.True(t, IsElfFile(elfPath))
257+
isElf, err := IsElfFile(elfPath)
258+
require.NoError(t, err)
259+
assert.True(t, isElf)
256260
})
257261

258-
t.Run("non-ELF file", func(t *testing.T) {
262+
t.Run("non-ELF file returns false nil", func(t *testing.T) {
259263
tempFile, err := os.CreateTemp("", "test_non_elf_*")
260264
require.NoError(t, err)
261265
defer os.Remove(tempFile.Name())
@@ -264,20 +268,58 @@ func TestIsElfFile(t *testing.T) {
264268
require.NoError(t, err)
265269
tempFile.Close()
266270

267-
assert.False(t, IsElfFile(tempFile.Name()))
271+
isElf, err := IsElfFile(tempFile.Name())
272+
require.NoError(t, err)
273+
assert.False(t, isElf)
274+
})
275+
276+
t.Run("non-existent file returns error", func(t *testing.T) {
277+
isElf, err := IsElfFile(filepath.Join(t.TempDir(), "does-not-exist"))
278+
require.Error(t, err)
279+
assert.True(t, errors.Is(err, os.ErrNotExist))
280+
assert.False(t, isElf)
268281
})
269282

270-
t.Run("non-existent file", func(t *testing.T) {
271-
assert.False(t, IsElfFile("/non/existent/file"))
283+
t.Run("permission denied returns error", func(t *testing.T) {
284+
if os.Getuid() == 0 {
285+
t.Skip("Running as root; permission test not meaningful")
286+
}
287+
tempFile, err := os.CreateTemp("", "test_noperm_*")
288+
require.NoError(t, err)
289+
defer os.Remove(tempFile.Name())
290+
tempFile.Close()
291+
292+
require.NoError(t, os.Chmod(tempFile.Name(), 0o000))
293+
294+
isElf, err := IsElfFile(tempFile.Name())
295+
require.Error(t, err)
296+
assert.True(t, errors.Is(err, os.ErrPermission))
297+
assert.False(t, isElf)
272298
})
273299

274-
t.Run("empty file", func(t *testing.T) {
300+
t.Run("short file returns false nil", func(t *testing.T) {
301+
tempFile, err := os.CreateTemp("", "test_short_*")
302+
require.NoError(t, err)
303+
defer os.Remove(tempFile.Name())
304+
305+
_, err = tempFile.Write([]byte{0x7F, 'E'})
306+
require.NoError(t, err)
307+
tempFile.Close()
308+
309+
isElf, err := IsElfFile(tempFile.Name())
310+
require.NoError(t, err)
311+
assert.False(t, isElf)
312+
})
313+
314+
t.Run("empty file returns false nil", func(t *testing.T) {
275315
tempFile, err := os.CreateTemp("", "test_empty_*")
276316
require.NoError(t, err)
277317
defer os.Remove(tempFile.Name())
278318
tempFile.Close()
279319

280-
assert.False(t, IsElfFile(tempFile.Name()))
320+
isElf, err := IsElfFile(tempFile.Name())
321+
require.NoError(t, err)
322+
assert.False(t, isElf)
281323
})
282324
}
283325

0 commit comments

Comments
 (0)