From baa8bd72e0ad151e822459ea46a479680d60637e Mon Sep 17 00:00:00 2001 From: Delyan Kratunov <167903925+dkratunov@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:56:58 -0800 Subject: [PATCH 1/5] python: fall back to Py_Version when filename lacks a version Some Python distributions ship a statically linked libpython and a `python3` binary without a version suffix, so regex-based version parsing fails. When the path does not expose a version, read the Py_Version runtime symbol from the target process and derive major/minor from its hex value. This restores version detection for standalone/embedded Python builds and keeps the loader from failing to compile due to an undeclared err variable. --- interpreter/python/python.go | 54 ++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/interpreter/python/python.go b/interpreter/python/python.go index 7360b5b54..7ad53bde2 100644 --- a/interpreter/python/python.go +++ b/interpreter/python/python.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "path" "reflect" "regexp" "slices" @@ -48,6 +49,25 @@ func pythonVer(major, minor int) uint16 { return uint16(major)*0x100 + uint16(minor) } +func readPyVersionHex(ef *pfelf.File) (int, int, uint32, error) { + addr, err := ef.LookupSymbolAddress("Py_Version") + if err != nil { + return 0, 0, 0, err + } + rm := ef.GetRemoteMemory() + versionHex := rm.Uint32(libpf.Address(addr)) + if versionHex == 0 { + return 0, 0, versionHex, errors.New("Py_Version is 0") + } + + major := int((versionHex >> 24) & 0xff) + minor := int((versionHex >> 16) & 0xff) + if major == 0 { + return 0, 0, versionHex, fmt.Errorf("invalid Py_Version 0x%x", versionHex) + } + return major, minor, versionHex, nil +} + //nolint:lll type pythonData struct { version uint16 @@ -673,18 +693,42 @@ func decodeStub(ef *pfelf.File, memoryBase libpf.SymbolValue, func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpreter.Data, error) { mainDSO := false + major := 0 + minor := 0 + var ef *pfelf.File + var err error matches := libpythonRegex.FindStringSubmatch(info.FileName()) if matches == nil { mainDSO = true matches = pythonRegex.FindStringSubmatch(info.FileName()) - if matches == nil { + } + if matches == nil { + if strings.HasPrefix(path.Base(info.FileName()), "python") { + elfFile, err := info.GetELF() + if err != nil { + return nil, err + } + ef = elfFile + var versionErr error + major, minor, _, versionErr = readPyVersionHex(ef) + if versionErr != nil { + return nil, nil + } + mainDSO = true + } else { return nil, nil } + } else { + major, _ = strconv.Atoi(matches[1]) + minor, _ = strconv.Atoi(matches[2]) } - ef, err := info.GetELF() - if err != nil { - return nil, err + if ef == nil { + elfFile, err := info.GetELF() + if err != nil { + return nil, err + } + ef = elfFile } if mainDSO { @@ -701,8 +745,6 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr } var pyruntimeAddr, autoTLSKey libpf.SymbolValue - major, _ := strconv.Atoi(matches[1]) - minor, _ := strconv.Atoi(matches[2]) version := pythonVer(major, minor) minVer := pythonVer(3, 6) From ca3ce001a423d00042e80cc157e3d0d5bc145ef9 Mon Sep 17 00:00:00 2001 From: Delyan Kratunov Date: Tue, 17 Feb 2026 15:39:06 -0800 Subject: [PATCH 2/5] review feedback --- interpreter/python/python.go | 49 ++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/interpreter/python/python.go b/interpreter/python/python.go index 7ad53bde2..2c75d29f3 100644 --- a/interpreter/python/python.go +++ b/interpreter/python/python.go @@ -44,28 +44,30 @@ var ( libpythonRegex = regexp.MustCompile(`^(?:.*/)?libpython(\d)\.(\d+)[^/]*`) ) -// pythonVer builds a version number from readable numbers +// pythonVer builds a version number from readable numbers. func pythonVer(major, minor int) uint16 { return uint16(major)*0x100 + uint16(minor) } -func readPyVersionHex(ef *pfelf.File) (int, int, uint32, error) { +func readPyVersionHex(ef *pfelf.File) (major uint8, minor uint8, err error) { + // Py_Version is referenced in CPython internals for versioned Python binaries. + // https://github.com/python/cpython/blob/v3.11.0/Doc/c-api/apiabiversion.rst addr, err := ef.LookupSymbolAddress("Py_Version") if err != nil { - return 0, 0, 0, err + return 0, 0, err } rm := ef.GetRemoteMemory() versionHex := rm.Uint32(libpf.Address(addr)) if versionHex == 0 { - return 0, 0, versionHex, errors.New("Py_Version is 0") + return 0, 0, errors.New("Py_Version is 0") } - major := int((versionHex >> 24) & 0xff) - minor := int((versionHex >> 16) & 0xff) + major = uint8((versionHex >> 24) & 0xff) + minor = uint8((versionHex >> 16) & 0xff) if major == 0 { - return 0, 0, versionHex, fmt.Errorf("invalid Py_Version 0x%x", versionHex) + return 0, 0, fmt.Errorf("invalid Py_Version 0x%x", versionHex) } - return major, minor, versionHex, nil + return major, minor, nil } //nolint:lll @@ -695,27 +697,13 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr mainDSO := false major := 0 minor := 0 - var ef *pfelf.File - var err error matches := libpythonRegex.FindStringSubmatch(info.FileName()) if matches == nil { mainDSO = true matches = pythonRegex.FindStringSubmatch(info.FileName()) } if matches == nil { - if strings.HasPrefix(path.Base(info.FileName()), "python") { - elfFile, err := info.GetELF() - if err != nil { - return nil, err - } - ef = elfFile - var versionErr error - major, minor, _, versionErr = readPyVersionHex(ef) - if versionErr != nil { - return nil, nil - } - mainDSO = true - } else { + if !strings.HasPrefix(path.Base(info.FileName()), "python") { return nil, nil } } else { @@ -723,12 +711,17 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr minor, _ = strconv.Atoi(matches[2]) } - if ef == nil { - elfFile, err := info.GetELF() - if err != nil { - return nil, err + ef, err := info.GetELF() + if err != nil { + return nil, err + } + if major == 0 { + majorFromSym, minorFromSym, versionErr := readPyVersionHex(ef) + if versionErr != nil { + return nil, nil } - ef = elfFile + major = int(majorFromSym) + minor = int(minorFromSym) } if mainDSO { From 5f0d1d6d9433523f0ab51b416e6bcbf7ff7932a5 Mon Sep 17 00:00:00 2001 From: Delyan Kratunov Date: Wed, 18 Feb 2026 09:57:56 -0800 Subject: [PATCH 3/5] python: avoid int-to-uint16 narrowing in pythonVer --- interpreter/python/python.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interpreter/python/python.go b/interpreter/python/python.go index 6ca4ab002..9765c171a 100644 --- a/interpreter/python/python.go +++ b/interpreter/python/python.go @@ -44,9 +44,9 @@ var ( libpythonRegex = regexp.MustCompile(`^(?:.*/)?libpython(\d)\.(\d+)[^/]*`) ) -// pythonVer builds a version number from readable numbers. -func pythonVer(major, minor int) uint16 { - return uint16(major)*0x100 + uint16(minor) +// pythonVer builds a version number from readable numbers +func pythonVer(major, minor uint16) uint16 { + return major*0x100 + minor } func readPyVersionHex(ef *pfelf.File) (major uint8, minor uint8, err error) { @@ -772,7 +772,7 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr } var pyruntimeAddr, autoTLSKey libpf.SymbolValue - version := pythonVer(major, minor) + version := pythonVer(uint16(major), uint16(minor)) minVer := pythonVer(3, 6) maxVer := pythonVer(3, 14) From 06a7dad2edb0bb346c8d1763945853f8651e9ac7 Mon Sep 17 00:00:00 2001 From: Delyan Kratunov Date: Fri, 20 Mar 2026 11:39:30 -0700 Subject: [PATCH 4/5] python: address codeql atoi --- interpreter/python/python.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/interpreter/python/python.go b/interpreter/python/python.go index c483d6688..c3bbe1bb6 100644 --- a/interpreter/python/python.go +++ b/interpreter/python/python.go @@ -743,8 +743,8 @@ func decodeStub(ef *pfelf.File, memoryBase libpf.SymbolValue, func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpreter.Data, error) { mainDSO := false - major := 0 - minor := 0 + major := uint16(0) + minor := uint16(0) matches := libpythonRegex.FindStringSubmatch(info.FileName()) if matches == nil { mainDSO = true @@ -755,8 +755,10 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr return nil, nil } } else { - major, _ = strconv.Atoi(matches[1]) - minor, _ = strconv.Atoi(matches[2]) + majorValue, _ := strconv.ParseUint(matches[1], 10, 16) + minorValue, _ := strconv.ParseUint(matches[2], 10, 16) + major = uint16(majorValue) + minor = uint16(minorValue) } ef, err := info.GetELF() @@ -768,8 +770,8 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr if versionErr != nil { return nil, nil } - major = int(majorFromSym) - minor = int(minorFromSym) + major = uint16(majorFromSym) + minor = uint16(minorFromSym) } if mainDSO { @@ -786,7 +788,7 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr } var pyruntimeAddr, autoTLSKey libpf.SymbolValue - version := pythonVer(uint16(major), uint16(minor)) + version := pythonVer(major, minor) minVer := pythonVer(3, 6) maxVer := pythonVer(3, 14) From f07186a2ed040e068c1bcb140599dddabdec2b6d Mon Sep 17 00:00:00 2001 From: Delyan Kratunov <167903925+dkratunov@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:26:03 -0700 Subject: [PATCH 5/5] skip versionHex explicit check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Timo Teräs --- interpreter/python/python.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/interpreter/python/python.go b/interpreter/python/python.go index c3bbe1bb6..c4c96c118 100644 --- a/interpreter/python/python.go +++ b/interpreter/python/python.go @@ -58,10 +58,6 @@ func readPyVersionHex(ef *pfelf.File) (major uint8, minor uint8, err error) { } rm := ef.GetRemoteMemory() versionHex := rm.Uint32(libpf.Address(addr)) - if versionHex == 0 { - return 0, 0, errors.New("Py_Version is 0") - } - major = uint8((versionHex >> 24) & 0xff) minor = uint8((versionHex >> 16) & 0xff) if major == 0 {