diff --git a/go.mod b/go.mod index ec5ecb8..b2df84e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/kubeshark/tracer -go 1.22.2 +go 1.22.4 toolchain go1.22.7 @@ -21,6 +21,7 @@ require ( github.com/knightsc/gapstone v0.0.0-20191231144527-6fa5afaf11a9 github.com/kubeshark/api v1.1.32 github.com/kubeshark/gopacket v1.1.30 + github.com/kubeshark/offsetdb v0.0.0-20250529155907-b5b962037b6b github.com/kubeshark/procfs v0.0.0-20250312150455-4b9efb18c324 github.com/kubeshark/tracerproto v1.0.3-0.20240730073449-de3a99a3719c github.com/kubeshark/utils v0.0.0-20250210221556-322c90ef9b16 diff --git a/go.sum b/go.sum index f43d408..cc8cbdc 100644 --- a/go.sum +++ b/go.sum @@ -133,6 +133,8 @@ github.com/kubeshark/api v1.1.32 h1:qZ4so2FBJOgw7Eqmlvr1Kz29hALM4hsFgHzC/cULoMM= github.com/kubeshark/api v1.1.32/go.mod h1:+Ua35OiwreWiUYfqJz0Aswn3UmsLctLUQh3tvxagQz4= github.com/kubeshark/gopacket v1.1.30 h1:Dz6eo7b6+NdVCrgiyKxlGEVTm0L6PwgbVvSomsuwIyU= github.com/kubeshark/gopacket v1.1.30/go.mod h1:Qo8/i/tdT74CCT7/pjO0L55Pktv5dQfj7M/Arv8MKm8= +github.com/kubeshark/offsetdb v0.0.0-20250529155907-b5b962037b6b h1:U0/ZtIE0O7T//FERnXxGDdLnEpDd2Q4E8Rr7UpPC9Vs= +github.com/kubeshark/offsetdb v0.0.0-20250529155907-b5b962037b6b/go.mod h1:qynxtfFHWWfNBVNSYHdW4x5+uiBFvV6eD7oEFiAhpak= github.com/kubeshark/procfs v0.0.0-20250312150455-4b9efb18c324 h1:bkkaNmy70yebjPIPeZv5bDamFl0fmAKQn038NWf/GBU= github.com/kubeshark/procfs v0.0.0-20250312150455-4b9efb18c324/go.mod h1:eOQ5k+THNnxNiyNdy1cWkGWTecBMrbxT1IHjdt6kLsY= github.com/kubeshark/tracerproto v1.0.0/go.mod h1:+efDYkwXxwakmHRpxHVEekyXNtg/aFx0uSo/I0lGV9k= diff --git a/pkg/discoverer/pids.go b/pkg/discoverer/pids.go index d150bc1..92ff871 100644 --- a/pkg/discoverer/pids.go +++ b/pkg/discoverer/pids.go @@ -8,7 +8,6 @@ import ( "regexp" "strconv" "strings" - "unsafe" "github.com/cilium/ebpf/link" @@ -138,7 +137,6 @@ func (p *pids) untargetCgroup(cgroupId uint64) { func (p *pids) handleFoundNewPIDs() { for { record, err := p.readerFoundPid.Read() - if err != nil { if errors.Is(err, perf.ErrClosed) { log.Info().Msg("found pid handler is closed") diff --git a/pkg/hooks/ssl/ssllib_hooks.go b/pkg/hooks/ssl/ssllib_hooks.go index ff918bc..fcd3f86 100644 --- a/pkg/hooks/ssl/ssllib_hooks.go +++ b/pkg/hooks/ssl/ssllib_hooks.go @@ -1,15 +1,29 @@ package ssl import ( + "debug/elf" + "fmt" "path/filepath" + "strconv" + "sync" "github.com/cilium/ebpf/link" "github.com/go-errors/errors" lru "github.com/hashicorp/golang-lru/v2" + "github.com/kubeshark/offsetdb/hasher" + "github.com/kubeshark/offsetdb/models" + "github.com/kubeshark/offsetdb/store" "github.com/kubeshark/tracer/pkg/bpf" "github.com/kubeshark/tracer/pkg/utils" + "github.com/rs/zerolog/log" ) +const ( + offsetdb = "/app/offsets.json" +) + +var offStore = store.NewOffsetStore() + type SslHooks struct { links []link.Link } @@ -17,7 +31,22 @@ type SslHooks struct { // TODO: incapsulate, add devuce id to the key, delete on file is deleted var hookInodes, _ = lru.New[uint64, uint32](16384) +func init() { + if err := offStore.LoadOffsets(offsetdb); err != nil { + } +} + func (s *SslHooks) InstallUprobes(bpfObjects *bpf.BpfObjects, sslLibraryPath string) error { + var once sync.Once + onceFunc := func() { + err := offStore.LoadOffsets(offsetdb) + if err != nil { + log.Warn().Msgf("failed to load offset db: %v", err) + offStore = nil + } + } + once.Do(onceFunc) + var isEnvoy bool if filepath.Base(sslLibraryPath) == "envoy" { isEnvoy = true @@ -32,13 +61,30 @@ func (s *SslHooks) InstallUprobes(bpfObjects *bpf.BpfObjects, sslLibraryPath str } sslLibrary, err := link.OpenExecutable(sslLibraryPath) - if err != nil { return errors.Wrap(err, 0) } if isEnvoy { - return s.installEnvoySslHooks(bpfObjects, sslLibrary) + // Compute the has of the binary + hash, err := hasher.ComputeFileSHA256(sslLibraryPath) + if err != nil { + return fmt.Errorf("fallback: sha256 failed: %w", err) + } + + // Check if the hash is in the offset store + var info *models.OffsetInfo + var found bool + if offStore != nil { + info, found = offStore.GetOffsets(hash) + } + // Check if the hash is in the offset store + if !found { + // Try to install the hooks by symbols + return s.installEnvoySslHooks(bpfObjects, sslLibrary) + } + + return s.installEnvoySslHooksWithOffset(bpfObjects, sslLibrary, sslLibraryPath, info) } return s.installSslHooks(bpfObjects, sslLibrary) @@ -145,3 +191,109 @@ func (s *SslHooks) Close() []error { return returnValue } + +func (s *SslHooks) installEnvoySslHooksWithOffset( + bpfObjects *bpf.BpfObjects, + sslLibrary *link.Executable, + sslLibraryPath string, + info *models.OffsetInfo, +) error { + var err error + var relativeOffset, baseOffset, absoluteOffset uint64 + + baseOffset, err = findStrippedExecutableSegmentOffset(sslLibraryPath) + if err != nil { + return fmt.Errorf("failed to find base offset in SSL library '%s': %w", sslLibraryPath, err) + } + + // --- SSL_write --- + if relativeOffset, err = parseOffset(info.SSLWriteOffset); err != nil { + return fmt.Errorf("parsing SSLWriteOffset: %w", err) + } + absoluteOffset = baseOffset + relativeOffset + + // ENTRY SSL_write + upWrite, err := sslLibrary.Uprobe( + "", + bpfObjects.BpfObjs.SslWrite, + &link.UprobeOptions{Address: absoluteOffset}, + ) + if err != nil { + return fmt.Errorf("attaching SSL_write uprobe at offset 0x%x : %w", absoluteOffset, err) + } + s.links = append(s.links, upWrite) + + // EXIT SSL_write (uses the same address as the entry) + urWrite, err := sslLibrary.Uretprobe( + "", + bpfObjects.BpfObjs.SslRetWrite, + &link.UprobeOptions{Address: absoluteOffset}, + ) + if err != nil { + return fmt.Errorf("attaching SSL_write uretprobe at offset 0x%x : %w", absoluteOffset, err) + } + s.links = append(s.links, urWrite) + + // --- SSL_read --- + if relativeOffset, err = parseOffset(info.SSLReadOffset); err != nil { + return fmt.Errorf("parsing SSLReadOffset: %w", err) + } + absoluteOffset = baseOffset + relativeOffset + + // ENTRY SSL_read + upRead, err := sslLibrary.Uprobe( + "", + bpfObjects.BpfObjs.SslRead, + &link.UprobeOptions{Address: absoluteOffset}, + ) + if err != nil { + return fmt.Errorf("attaching SSL_read uprobe at offset 0x%x : %w", absoluteOffset, err) + } + s.links = append(s.links, upRead) + + // EXIT SSL_read (uses the same address as the entry) + urRead, err := sslLibrary.Uretprobe( + "", + bpfObjects.BpfObjs.SslRetRead, + &link.UprobeOptions{Address: absoluteOffset}, + ) + if err != nil { + return fmt.Errorf("attaching SSL_read uretprobe at offset 0x%x : %w", absoluteOffset, err) + } + s.links = append(s.links, urRead) + + return nil +} + +// parseOffset turns a hex- or dec-formatted string into a uint64 +func parseOffset(s string) (uint64, error) { + // Let strconv auto-detect the base from “0x…” prefix or plain digits + val, err := strconv.ParseUint(s, 0, 64) + if err != nil { + return 0, fmt.Errorf("invalid offset %q: %w", s, err) + } + return val, nil +} + +// findStrippedExecutableSegmentOffset finds the file offset of the first executable segment +// or the .text section in an ELF file. +func findStrippedExecutableSegmentOffset(path string) (uint64, error) { + f, err := elf.Open(path) + if err != nil { + return 0, fmt.Errorf("elf.Open %s: %w", path, err) + } + defer f.Close() + + // Prefer .text section offset when available + if sec := f.Section(".text"); sec != nil && sec.Offset != 0 { + return sec.Offset, nil + } + + // Otherwise, pick the first executable PT_LOAD + for _, prog := range f.Progs { + if prog.Type == elf.PT_LOAD && (prog.Flags&elf.PF_X) != 0 { + return prog.Off, nil + } + } + return 0, errors.New("no executable segment or .text section found") +}