fix(ja4ebpf): correct BPF struct byte offsets and regenerate SSL_write programs
Fix two critical offset bugs introduced when ip_total_length was added to tcp_syn_event: tcp_options_raw offset 21→23 and tcp_options_len offset 61→63, plus minimum size check 70→72. Fix ssl_data_event direction field offset from 4118 (inside timestamp_ns) to 4126. Simplify attachSSLWrite to use generated objects directly instead of dynamic spec loading. Regenerate BPF objects with SSL_write uprobe programs included. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -309,9 +309,9 @@ func consumeSynEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
|
|||||||
// src_ip(4)+dst_ip(4)+src_port(2)+dst_port(2)+ttl(1)+df_bit(1)+ip_id(2)+
|
// src_ip(4)+dst_ip(4)+src_port(2)+dst_port(2)+ttl(1)+df_bit(1)+ip_id(2)+
|
||||||
// ip_total_length(2)+window_size(2)+window_scale(1)+mss(2)+tcp_options_raw[40]+
|
// ip_total_length(2)+window_size(2)+window_scale(1)+mss(2)+tcp_options_raw[40]+
|
||||||
// tcp_options_len(1)+timestamp_ns(8)
|
// tcp_options_len(1)+timestamp_ns(8)
|
||||||
// offsets: 0 4 8 10 12 13 14 16 18 19 21 61
|
// offsets: 0 4 8 10 12 13 14 16 18 20 21 23 63
|
||||||
// total = 62 + 8 = 70
|
// total = 64 + 8 = 72
|
||||||
if len(record.RawSample) < 70 {
|
if len(record.RawSample) < 72 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
data := record.RawSample
|
data := record.RawSample
|
||||||
@ -342,12 +342,12 @@ func consumeSynEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
|
|||||||
ipTotalLength := binary.LittleEndian.Uint16(data[16:18])
|
ipTotalLength := binary.LittleEndian.Uint16(data[16:18])
|
||||||
windowSize := binary.LittleEndian.Uint16(data[18:20])
|
windowSize := binary.LittleEndian.Uint16(data[18:20])
|
||||||
|
|
||||||
optLen := int(data[61])
|
optLen := int(data[63])
|
||||||
if optLen > 40 {
|
if optLen > 40 {
|
||||||
optLen = 40
|
optLen = 40
|
||||||
}
|
}
|
||||||
tcpOpts := make([]byte, optLen)
|
tcpOpts := make([]byte, optLen)
|
||||||
copy(tcpOpts, data[21:21+optLen])
|
copy(tcpOpts, data[23:23+optLen])
|
||||||
|
|
||||||
// Analyser les options TCP brutes pour extraire MSS et Window Scale
|
// Analyser les options TCP brutes pour extraire MSS et Window Scale
|
||||||
mss, windowScale := parseTCPOptions(tcpOpts)
|
mss, windowScale := parseTCPOptions(tcpOpts)
|
||||||
@ -499,12 +499,13 @@ func consumeSSLEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
|
|||||||
srcIPRaw := binary.LittleEndian.Uint32(data[12:16])
|
srcIPRaw := binary.LittleEndian.Uint32(data[12:16])
|
||||||
srcPort := binary.LittleEndian.Uint16(data[16:18])
|
srcPort := binary.LittleEndian.Uint16(data[16:18])
|
||||||
|
|
||||||
// data[4096] commence à offset 18, data_len à offset 4114, direction à offset 4118
|
// data[4096] commence à offset 18, data_len à offset 4114,
|
||||||
if len(data) < 4119 {
|
// timestamp_ns à offset 4118, direction à offset 4126
|
||||||
|
if len(data) < 4127 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dataLen := binary.LittleEndian.Uint32(data[4114:4118])
|
dataLen := binary.LittleEndian.Uint32(data[4114:4118])
|
||||||
direction := data[4118] // 0 = SSL_read (client→serveur), 1 = SSL_write (serveur→client)
|
direction := data[4126] // 0 = SSL_read (client→serveur), 1 = SSL_write (serveur→client)
|
||||||
if dataLen > 4096 {
|
if dataLen > 4096 {
|
||||||
dataLen = 4096
|
dataLen = 4096
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,11 +107,13 @@ type Ja4SslSpecs struct {
|
|||||||
//
|
//
|
||||||
// It can be passed ebpf.CollectionSpec.Assign.
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
type Ja4SslProgramSpecs struct {
|
type Ja4SslProgramSpecs struct {
|
||||||
KprobeAccept4Entry *ebpf.ProgramSpec `ebpf:"kprobe_accept4_entry"`
|
KprobeAccept4Entry *ebpf.ProgramSpec `ebpf:"kprobe_accept4_entry"`
|
||||||
KretprobeAccept4Exit *ebpf.ProgramSpec `ebpf:"kretprobe_accept4_exit"`
|
KretprobeAccept4Exit *ebpf.ProgramSpec `ebpf:"kretprobe_accept4_exit"`
|
||||||
UprobeSslReadEntry *ebpf.ProgramSpec `ebpf:"uprobe_ssl_read_entry"`
|
UprobeSslReadEntry *ebpf.ProgramSpec `ebpf:"uprobe_ssl_read_entry"`
|
||||||
UprobeSslSetFd *ebpf.ProgramSpec `ebpf:"uprobe_ssl_set_fd"`
|
UprobeSslSetFd *ebpf.ProgramSpec `ebpf:"uprobe_ssl_set_fd"`
|
||||||
UretprobeSslReadExit *ebpf.ProgramSpec `ebpf:"uretprobe_ssl_read_exit"`
|
UprobeSslWriteEntry *ebpf.ProgramSpec `ebpf:"uprobe_ssl_write_entry"`
|
||||||
|
UretprobeSslReadExit *ebpf.ProgramSpec `ebpf:"uretprobe_ssl_read_exit"`
|
||||||
|
UretprobeSslWriteExit *ebpf.ProgramSpec `ebpf:"uretprobe_ssl_write_exit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ja4SslMapSpecs contains maps before they are loaded into the kernel.
|
// Ja4SslMapSpecs contains maps before they are loaded into the kernel.
|
||||||
@ -189,11 +191,13 @@ func (m *Ja4SslMaps) Close() error {
|
|||||||
//
|
//
|
||||||
// It can be passed to LoadJa4SslObjects or ebpf.CollectionSpec.LoadAndAssign.
|
// It can be passed to LoadJa4SslObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
type Ja4SslPrograms struct {
|
type Ja4SslPrograms struct {
|
||||||
KprobeAccept4Entry *ebpf.Program `ebpf:"kprobe_accept4_entry"`
|
KprobeAccept4Entry *ebpf.Program `ebpf:"kprobe_accept4_entry"`
|
||||||
KretprobeAccept4Exit *ebpf.Program `ebpf:"kretprobe_accept4_exit"`
|
KretprobeAccept4Exit *ebpf.Program `ebpf:"kretprobe_accept4_exit"`
|
||||||
UprobeSslReadEntry *ebpf.Program `ebpf:"uprobe_ssl_read_entry"`
|
UprobeSslReadEntry *ebpf.Program `ebpf:"uprobe_ssl_read_entry"`
|
||||||
UprobeSslSetFd *ebpf.Program `ebpf:"uprobe_ssl_set_fd"`
|
UprobeSslSetFd *ebpf.Program `ebpf:"uprobe_ssl_set_fd"`
|
||||||
UretprobeSslReadExit *ebpf.Program `ebpf:"uretprobe_ssl_read_exit"`
|
UprobeSslWriteEntry *ebpf.Program `ebpf:"uprobe_ssl_write_entry"`
|
||||||
|
UretprobeSslReadExit *ebpf.Program `ebpf:"uretprobe_ssl_read_exit"`
|
||||||
|
UretprobeSslWriteExit *ebpf.Program `ebpf:"uretprobe_ssl_write_exit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Ja4SslPrograms) Close() error {
|
func (p *Ja4SslPrograms) Close() error {
|
||||||
@ -202,7 +206,9 @@ func (p *Ja4SslPrograms) Close() error {
|
|||||||
p.KretprobeAccept4Exit,
|
p.KretprobeAccept4Exit,
|
||||||
p.UprobeSslReadEntry,
|
p.UprobeSslReadEntry,
|
||||||
p.UprobeSslSetFd,
|
p.UprobeSslSetFd,
|
||||||
|
p.UprobeSslWriteEntry,
|
||||||
p.UretprobeSslReadExit,
|
p.UretprobeSslReadExit,
|
||||||
|
p.UretprobeSslWriteExit,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -266,10 +266,9 @@ func (l *Loader) AttachUprobes(sslLibPath string) error {
|
|||||||
l.uprobeLinks = append(l.uprobeLinks, readExitLink)
|
l.uprobeLinks = append(l.uprobeLinks, readExitLink)
|
||||||
|
|
||||||
// SSL_write — capture les réponses HTTP du serveur (direction=1)
|
// SSL_write — capture les réponses HTTP du serveur (direction=1)
|
||||||
// Les programmes BPF uprobe/SSL_write et uretprobe/SSL_write sont
|
if err := l.attachSSLWrite(ex); err != nil {
|
||||||
// chargés depuis les objets Ja4Ssl. Si les objets BPF n'ont pas été
|
return fmt.Errorf("attachement SSL_write: %w", err)
|
||||||
// régénérés (pas de clang sur le host), ces programmes sont absents.
|
}
|
||||||
_ = l.attachSSLWrite(ex)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -293,43 +292,17 @@ func (l *Loader) AttachAcceptProbe() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// attachSSLWrite tente d'attacher les uprobes SSL_write pour capturer
|
// attachSSLWrite attache les uprobes SSL_write pour capturer
|
||||||
// les réponses HTTP du serveur. Si les programmes BPF SSL_write ne sont
|
// les réponses HTTP du serveur (direction=1).
|
||||||
// pas disponibles (objets non régénérés), retourne nil sans bloquer.
|
|
||||||
func (l *Loader) attachSSLWrite(ex *link.Executable) error {
|
func (l *Loader) attachSSLWrite(ex *link.Executable) error {
|
||||||
// Charger la collection spec embarquée pour vérifier si SSL_write existe
|
entryLink, err := ex.Uprobe("SSL_write", l.sslObjs.UprobeSslWriteEntry, nil)
|
||||||
spec, err := LoadJa4Ssl()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
entrySpec, hasEntry := spec.Programs["uprobe_ssl_write_entry"]
|
|
||||||
exitSpec, hasExit := spec.Programs["uretprobe_ssl_write_exit"]
|
|
||||||
if !hasEntry || !hasExit {
|
|
||||||
return nil // programmes SSL_write absents — BPF non régénéré
|
|
||||||
}
|
|
||||||
|
|
||||||
writeEntry, err := ebpf.NewProgram(entrySpec)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
writeExit, err := ebpf.NewProgram(exitSpec)
|
|
||||||
if err != nil {
|
|
||||||
writeEntry.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
entryLink, err := ex.Uprobe("SSL_write", writeEntry, nil)
|
|
||||||
if err != nil {
|
|
||||||
writeEntry.Close()
|
|
||||||
writeExit.Close()
|
|
||||||
return fmt.Errorf("attachement uprobe SSL_write (entry): %w", err)
|
return fmt.Errorf("attachement uprobe SSL_write (entry): %w", err)
|
||||||
}
|
}
|
||||||
l.uprobeLinks = append(l.uprobeLinks, entryLink)
|
l.uprobeLinks = append(l.uprobeLinks, entryLink)
|
||||||
|
|
||||||
exitLink, err := ex.Uretprobe("SSL_write", writeExit, nil)
|
exitLink, err := ex.Uretprobe("SSL_write", l.sslObjs.UretprobeSslWriteExit, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeExit.Close()
|
|
||||||
return fmt.Errorf("attachement uretprobe SSL_write (exit): %w", err)
|
return fmt.Errorf("attachement uretprobe SSL_write (exit): %w", err)
|
||||||
}
|
}
|
||||||
l.uprobeLinks = append(l.uprobeLinks, exitLink)
|
l.uprobeLinks = append(l.uprobeLinks, exitLink)
|
||||||
|
|||||||
Reference in New Issue
Block a user