-
Notifications
You must be signed in to change notification settings - Fork 685
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Read LINUX_VERSION_CODE from vDSO ELF header instead of uname
- Loading branch information
1 parent
89c6acb
commit 2a3b945
Showing
9 changed files
with
242 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package internal | ||
|
||
import ( | ||
"debug/elf" | ||
"encoding/binary" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"math" | ||
"os" | ||
|
||
"github.com/cilium/ebpf/internal/unix" | ||
) | ||
|
||
var ( | ||
errAuxvNoVDSO = errors.New("no vdso address found in auxv") | ||
) | ||
|
||
// vdsoVersion returns the LINUX_VERSION_CODE embedded in the vDSO library | ||
// linked into the current process image. | ||
func vdsoVersion() (uint32, error) { | ||
// Read data from the auxiliary vector, which is normally passed directly | ||
// to the process. Go does not expose that data, so we must read it from procfs. | ||
// https://man7.org/linux/man-pages/man3/getauxval.3.html | ||
av, err := os.Open("/proc/self/auxv") | ||
if err != nil { | ||
return 0, fmt.Errorf("opening auxv: %w", err) | ||
} | ||
defer av.Close() | ||
|
||
vdsoAddr, err := vdsoMemoryAddress(av) | ||
if err != nil { | ||
return 0, fmt.Errorf("finding vDSO memory address: %w", err) | ||
} | ||
|
||
// Use /proc/self/mem rather than unsafe.Pointer tricks. | ||
mem, err := os.Open("/proc/self/mem") | ||
if err != nil { | ||
return 0, fmt.Errorf("opening mem: %w", err) | ||
} | ||
defer mem.Close() | ||
|
||
// Open ELF at provided memory address, as offset into /proc/self/mem. | ||
c, err := vdsoLinuxVersionCode(io.NewSectionReader(mem, int64(vdsoAddr), math.MaxInt64)) | ||
if err != nil { | ||
return 0, fmt.Errorf("reading linux version code: %w", err) | ||
} | ||
|
||
return c, nil | ||
} | ||
|
||
// vdsoMemoryAddress returns the memory address of the vDSO library | ||
// linked into the current process image. r is an io.Reader into an auxv blob. | ||
func vdsoMemoryAddress(r io.Reader) (uint64, error) { | ||
const ( | ||
_AT_NULL = 0 // End of vector | ||
_AT_SYSINFO_EHDR = 33 // Offset to vDSO blob in process image | ||
) | ||
|
||
// Loop through all tag/value pairs in auxv until we find `AT_SYSINFO_EHDR`, | ||
// the address of a page containing the virtual Dynamic Shared Object (vDSO). | ||
aux := struct{ Tag, Val uint64 }{} | ||
for { | ||
if err := binary.Read(r, NativeEndian, &aux); err != nil { | ||
return 0, fmt.Errorf("reading auxv entry: %w", err) | ||
} | ||
|
||
switch aux.Tag { | ||
case _AT_SYSINFO_EHDR: | ||
if aux.Val != 0 { | ||
return aux.Val, nil | ||
} | ||
return 0, fmt.Errorf("invalid vDSO address in auxv") | ||
// _AT_NULL is always the last tag/val pair in the aux vector | ||
// and can be treated like EOF. | ||
case _AT_NULL: | ||
return 0, errAuxvNoVDSO | ||
} | ||
} | ||
} | ||
|
||
// format described at https://www.man7.org/linux/man-pages/man5/elf.5.html in section 'Notes (Nhdr)' | ||
type elfNoteHeader struct { | ||
NameSize int32 | ||
DescSize int32 | ||
Type int32 | ||
} | ||
|
||
// vdsoLinuxVersionCode returns the LINUX_VERSION_CODE embedded in | ||
// the ELF notes section of the binary provided by the reader. | ||
func vdsoLinuxVersionCode(r io.ReaderAt) (uint32, error) { | ||
hdr, err := NewSafeELFFile(r) | ||
if err != nil { | ||
return 0, fmt.Errorf("reading vDSO ELF: %w", err) | ||
} | ||
|
||
sec := hdr.SectionByType(elf.SHT_NOTE) | ||
if sec == nil { | ||
return 0, fmt.Errorf("no note section found in vDSO ELF") | ||
} | ||
|
||
sr := sec.Open() | ||
var n elfNoteHeader | ||
|
||
// Read notes until we find one named 'Linux'. | ||
for { | ||
if err := binary.Read(sr, hdr.ByteOrder, &n); err != nil { | ||
if errors.Is(err, io.EOF) { | ||
return 0, fmt.Errorf("no Linux note in ELF") | ||
} | ||
return 0, fmt.Errorf("reading note header: %w", err) | ||
} | ||
|
||
// If a note name is defined, it follows the note header. | ||
var name string | ||
if n.NameSize > 0 { | ||
// Read the note name, aligned to 4 bytes. | ||
buf := make([]byte, Align(int(n.NameSize), 4)) | ||
if err := binary.Read(sr, hdr.ByteOrder, &buf); err != nil { | ||
return 0, fmt.Errorf("reading note name: %w", err) | ||
} | ||
|
||
// Read nul-terminated string. | ||
name = unix.ByteSliceToString(buf[:n.NameSize]) | ||
} | ||
|
||
// If a note descriptor is defined, it follows the name. | ||
// It is possible for a note to have a descriptor but not a name. | ||
if n.DescSize > 0 { | ||
// LINUX_VERSION_CODE is a uint32 value. | ||
if name == "Linux" && n.DescSize == 4 && n.Type == 0 { | ||
var version uint32 | ||
if err := binary.Read(sr, hdr.ByteOrder, &version); err != nil { | ||
return 0, fmt.Errorf("reading note descriptor: %w", err) | ||
} | ||
return version, nil | ||
} | ||
|
||
// Discard the note descriptor if it exists but we're not interested in it. | ||
if _, err := io.CopyN(io.Discard, sr, int64(Align(int(n.DescSize), 4))); err != nil { | ||
return 0, err | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package internal | ||
|
||
import ( | ||
"errors" | ||
"os" | ||
"testing" | ||
) | ||
|
||
func TestAuxvVDSOMemoryAddress(t *testing.T) { | ||
av, err := os.Open("../testdata/auxv.bin") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
t.Cleanup(func() { av.Close() }) | ||
|
||
addr, err := vdsoMemoryAddress(av) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
expected := uint64(0x7ffd377e5000) | ||
if addr != expected { | ||
t.Errorf("Expected vDSO memory address %x, got %x", expected, addr) | ||
} | ||
} | ||
|
||
func TestAuxvNoVDSO(t *testing.T) { | ||
// Copy of auxv.bin with the vDSO pointer removed. | ||
av, err := os.Open("../testdata/auxv_no_vdso.bin") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
t.Cleanup(func() { av.Close() }) | ||
|
||
_, err = vdsoMemoryAddress(av) | ||
if want, got := errAuxvNoVDSO, err; !errors.Is(got, want) { | ||
t.Fatalf("expected error '%v', got: %v", want, got) | ||
} | ||
} | ||
|
||
func TestLinuxVersionCodeEmbedded(t *testing.T) { | ||
vdso, err := os.Open("../testdata/vdso.bin") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
t.Cleanup(func() { vdso.Close() }) | ||
|
||
vc, err := vdsoLinuxVersionCode(vdso) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
expected := uint32(328828) // 5.4.124 | ||
if vc != expected { | ||
t.Errorf("Expected version code %d, got %d", expected, vc) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.