Skip to content

Commit

Permalink
[SIEM][Auditbeat] Fix IPv6 for kernels older than 3.13 (#13497) (#13523)
Browse files Browse the repository at this point in the history
This fixes a known problem with the system/socket dataset. Before 3.13
an extra level of indirection is needed to access IPv6 addresses from a
struct inet_sock.

Relates #13058

(cherry picked from commit dc6c013)
  • Loading branch information
adriansr authored Sep 6, 2019
1 parent 858f3ff commit 8604f0b
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 45 deletions.
48 changes: 40 additions & 8 deletions x-pack/auditbeat/module/system/socket/guess/cskxmit6.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@ func init() {
}

type guessInet6CskXmit struct {
guessInetSockIPv6
sock uintptr
acceptedFd int
ctx Context
loopback ipv6loopback
clientAddr, serverAddr unix.SockaddrInet6
client, server int
sock uintptr
acceptedFd int
}

// Name of this guess.
Expand Down Expand Up @@ -93,12 +96,39 @@ func (g *guessInet6CskXmit) Probes() ([]helper.ProbeDef, error) {

// Prepare setups an IPv6 TCP client/server where the server is listening
// and the client is connecting to it.
func (g *guessInet6CskXmit) Prepare(ctx Context) error {
func (g *guessInet6CskXmit) Prepare(ctx Context) (err error) {
g.ctx = ctx
g.acceptedFd = -1
if err := g.guessInetSockIPv6.Prepare(ctx); err != nil {
return err
g.loopback, err = newIPv6Loopback()
if err != nil {
return errors.Wrap(err, "detect IPv6 loopback failed")
}
defer func() {
if err != nil {
g.loopback.Cleanup()
}
}()
clientIP, err := g.loopback.addRandomAddress()
if err != nil {
return errors.Wrap(err, "failed adding first device address")
}
serverIP, err := g.loopback.addRandomAddress()
if err != nil {
return errors.Wrap(err, "failed adding second device address")
}
copy(g.clientAddr.Addr[:], clientIP)
copy(g.serverAddr.Addr[:], serverIP)

if g.client, g.clientAddr, err = createSocket6WithProto(unix.SOCK_STREAM, g.clientAddr); err != nil {
return errors.Wrap(err, "error creating server")
}
if g.server, g.serverAddr, err = createSocket6WithProto(unix.SOCK_STREAM, g.serverAddr); err != nil {
return errors.Wrap(err, "error creating client")
}
if err = unix.Listen(g.server, 1); err != nil {
return errors.Wrap(err, "error in listen")
}
if err := unix.Connect(g.client, &g.serverAddr); err != nil {
if err = unix.Connect(g.client, &g.serverAddr); err != nil {
return errors.Wrap(err, "connect failed")
}
return nil
Expand All @@ -109,7 +139,9 @@ func (g *guessInet6CskXmit) Terminate() error {
if g.acceptedFd != -1 {
unix.Close(g.acceptedFd)
}
return g.guessInetSockIPv6.Terminate()
unix.Close(g.client)
unix.Close(g.server)
return g.loopback.Cleanup()
}

// Trigger accepts the client connection on the server, triggering a call
Expand Down
8 changes: 8 additions & 0 deletions x-pack/auditbeat/module/system/socket/guess/inetsock.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import (
// INET_SOCK_LPORT : 582
// INET_SOCK_RADDR : 576
// INET_SOCK_RPORT : 580
// INET_SOCK_RADDR_LIST : [...array of offsets...]
// INET_SOCK_RADDR_LIST is a list of all the offsets within the structure that
// matched the remote address. This is used by guess_inet_sock6.

func init() {
if err := Registry.AddGuess(&guessInetSockIPv4{}); err != nil {
Expand All @@ -55,6 +58,10 @@ func (g *guessInetSockIPv4) Provides() []string {
"INET_SOCK_LPORT",
"INET_SOCK_RADDR",
"INET_SOCK_RPORT",
"INET_SOCK_LADDR_LIST",
"INET_SOCK_LPORT_LIST",
"INET_SOCK_RADDR_LIST",
"INET_SOCK_RPORT_LIST",
}
}

Expand Down Expand Up @@ -201,6 +208,7 @@ func (g *guessInetSockIPv4) Reduce(results []common.MapStr) (result common.MapSt
if err != nil {
return nil, err
}
result[key+"_LIST"] = list
result[key] = list[0]
}
return result, nil
Expand Down
214 changes: 190 additions & 24 deletions x-pack/auditbeat/module/system/socket/guess/inetsock6.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ package guess

import (
"bytes"
"fmt"
"strings"

"github.com/pkg/errors"
"golang.org/x/sys/unix"
Expand All @@ -19,14 +21,83 @@ import (

/*
Guess the offset of local and remote IPv6 addresses on a struct inet_sock*.
This is an easy one as the addresses appear consecutive in memory.
This guess is a two-in-one as the IPv6 addresses can appear in two
different ways:
before 3.13:
IPv6 addresses are accessed via a pointer to an ipv6_pinfo structure:
struct inet_sock {
struct sock sk;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
struct ipv6_pinfo *pinet6;
#endif
__be32 daddr;
[...]
struct ipv6_pinfo {
struct in6_addr saddr;
struct in6_addr rcv_saddr;
struct in6_addr daddr;
[...]
The guess finds this pointer easily because it just the field just
before the IPv4 destination address.
after 3.13:
Both addresses are consecutive fields in struct sock_common:
struct in6_addr skc_v6_daddr;
struct in6_addr skc_v6_rcv_saddr;
So what it does is:
Probe #1 dumps the inet_sock structure, and looks for the concatenation
of destination and source addresses.
Probe #2 dumps the 16 bytes pointed to by the pointer-aligned address
before all known offsets of the IPv4 destination address.
If the addresses are found by #1 it keeps the offsets. Otherwise it
keeps the offsets from #2.
The results of this guess are not offsets because it needs to accommodate
for different levels of indirection depending on the target kernel.
A "termination" variable is used:
In 3.13+:
INET_SOCK_V6_RADDR_A: "+56"
INET_SOCK_V6_TERM: ":u64"
so that a kprobe can define:
"{{.INET_SOCK_V6_RADDR_A}}({{.RET}}){{.INET_SOCK_V6_TERM}}"
resulting in:
"+56($retval):u64"
while before 3.13 it will be:
INET_SOCK_V6_RADDR_A: "+8(+512"
INET_SOCK_V6_TERM: "):u64"
and the same kprobe will result in:
"+8(+512($retval)):u64"
The same result could've been achieved by having the templates be
functions that can be {{call'ed}} in the template but that cause a lot
of trouble on its own:
- functions need to be defined for kprobe validation
-> defining placeholders breaks the guesses dependency resolution.
- passing template variables as function arguments causes the
templates to need to be resolved recursively.
Output:
INET_SOCK_V6_RADDR_A: 56
INET_SOCK_V6_RADDR_B: 64
INET_SOCK_V6_LADDR_A: 72
INET_SOCK_V6_LADDR_B: 80
INET_SOCK_V6_RADDR_A: +56
INET_SOCK_V6_RADDR_B: +64
INET_SOCK_V6_LADDR_A: +72
INET_SOCK_V6_LADDR_B: +80
*/

const inetSockDumpSize = 8 * 256
Expand All @@ -42,6 +113,9 @@ type guessInetSockIPv6 struct {
loopback ipv6loopback
clientAddr, serverAddr unix.SockaddrInet6
client, server int
offsets []int
fullDump []byte
ptrDump []byte
}

// Name of this guess.
Expand All @@ -63,23 +137,73 @@ func (g *guessInetSockIPv6) Provides() []string {
func (g *guessInetSockIPv6) Requires() []string {
return []string{
"RET",
"INET_SOCK_RADDR_LIST",
}
}

// eventWrapper is used to wrap events from one of the probes for differentiation.
type eventWrapper struct {
event interface{}
}

// decoderWrapper takes an inner decoder and wraps it so that the returned events
// are wrapped with the eventWrapper type.
type decoderWrapper struct {
inner tracing.Decoder
}

// Decode wraps events from the inner decoder into a the wrapper type.
func (d *decoderWrapper) Decode(raw []byte, meta tracing.Metadata) (event interface{}, err error) {
if event, err = d.inner.Decode(raw, meta); err != nil {
return event, err
}
return eventWrapper{event}, nil
}

// Probes returns a kretprobe in inet_csk_accept that dumps the memory pointed
// to by the return value (an inet_sock*).
func (g *guessInetSockIPv6) Probes() ([]helper.ProbeDef, error) {
return []helper.ProbeDef{
{
Probe: tracing.Probe{
Type: tracing.TypeKRetProbe,
Name: "inet_sock_ipv6_guess",
Address: "inet_csk_accept",
Fetchargs: helper.MakeMemoryDump("{{.RET}}", 0, inetSockDumpSize),
},
Decoder: tracing.NewDumpDecoder,
// to by the return value (an inet_sock*) and a kretprobe that dumps various
// candidates for the ipv6_pinfo struct.
func (g *guessInetSockIPv6) Probes() (probes []helper.ProbeDef, err error) {
probes = append(probes, helper.ProbeDef{
Probe: tracing.Probe{
Type: tracing.TypeKRetProbe,
Name: "inet_sock_ipv6_guess",
Address: "inet_csk_accept",
Fetchargs: helper.MakeMemoryDump("{{.RET}}", 0, inetSockDumpSize),
},
Decoder: tracing.NewDumpDecoder,
})
raddrOffsets := g.offsets
g.offsets = nil
const sizePtr = int(sizeOfPtr)
var fetch []string
for _, off := range raddrOffsets {
// the pointer we're looking for is after a field of sizeof(struct sock)
if off < sizePtr*2 {
continue
}
// This is aligning to the nearest offset aligned to sizePtr and smaller
// than off.
off = alignTo(off-2*sizePtr+1, sizePtr)
g.offsets = append(g.offsets, off)
// dumps the rcv_saddr field of struct ipv6_pinfo
fetch = append(fetch, fmt.Sprintf("+16(+%d({{.RET}})):u64 +24(+%d({{.RET}})):u64", off, off))
}
probes = append(probes, helper.ProbeDef{
Probe: tracing.Probe{
Type: tracing.TypeKRetProbe,
Name: "inet_sock_ipv6_guess2",
Address: "inet_csk_accept",
Fetchargs: strings.Join(fetch, " "),
},
Decoder: func(desc tracing.ProbeFormat) (decoder tracing.Decoder, err error) {
if decoder, err = tracing.NewDumpDecoder(desc); err != nil {
return nil, err
}
return &decoderWrapper{decoder}, nil
},
}, nil
})
return probes, nil
}

// Prepare creates an IPv6 client/server bound to loopback.
Expand All @@ -88,6 +212,10 @@ func (g *guessInetSockIPv6) Probes() ([]helper.ProbeDef, error) {
// random addresses in the fd00::/8 reserved network to the loopback device.
func (g *guessInetSockIPv6) Prepare(ctx Context) (err error) {
g.ctx = ctx
g.offsets, err = getListField(g.ctx.Vars, "INET_SOCK_RADDR_LIST")
if err != nil {
return err
}
g.loopback, err = newIPv6Loopback()
if err != nil {
return errors.Wrap(err, "detect IPv6 loopback failed")
Expand Down Expand Up @@ -133,10 +261,26 @@ func (g *guessInetSockIPv6) Trigger() error {
return nil
}

// Extract scans the returned memory dump for the remote address followed
// by the local address.
// Extract scans stores the events from the two different kprobes and then
// looks for the result in one of them.
func (g *guessInetSockIPv6) Extract(event interface{}) (common.MapStr, bool) {
raw := event.([]byte)
if w, ok := event.(eventWrapper); ok {
g.ptrDump = w.event.([]byte)
} else {
g.fullDump = event.([]byte)
}
if g.ptrDump == nil || g.fullDump == nil {
return nil, false
}

result, ok := g.searchStructSock(g.fullDump)
if !ok {
result, ok = g.searchIPv6PInfo(g.ptrDump)
}
return result, ok
}

func (g *guessInetSockIPv6) searchStructSock(raw []byte) (common.MapStr, bool) {
var expected []byte
expected = append(expected, g.clientAddr.Addr[:]...) // sck_v6_daddr
expected = append(expected, g.serverAddr.Addr[:]...) // sck_v6_rcv_saddr
Expand All @@ -145,10 +289,32 @@ func (g *guessInetSockIPv6) Extract(event interface{}) (common.MapStr, bool) {
return nil, false
}
return common.MapStr{
"INET_SOCK_V6_RADDR_A": offset,
"INET_SOCK_V6_RADDR_B": offset + 8,
"INET_SOCK_V6_LADDR_A": offset + 16,
"INET_SOCK_V6_LADDR_B": offset + 24,
"INET_SOCK_V6_TERM": ":u64",
"INET_SOCK_V6_RADDR_A": fmt.Sprintf("+%d", offset),
"INET_SOCK_V6_RADDR_B": fmt.Sprintf("+%d", offset+8),
"INET_SOCK_V6_LADDR_A": fmt.Sprintf("+%d", offset+16),
"INET_SOCK_V6_LADDR_B": fmt.Sprintf("+%d", offset+24),
"INET_SOCK_V6_LIMIT": offset,
}, true
}

func (g *guessInetSockIPv6) searchIPv6PInfo(raw []byte) (common.MapStr, bool) {
offset := bytes.Index(raw, g.serverAddr.Addr[:])
if offset == -1 {
return nil, false
}
idx := offset / 16 // length of IPv6 address
if idx >= len(g.offsets) {
return nil, false
}
off := g.offsets[idx]
return common.MapStr{
"INET_SOCK_V6_TERM": "):u64",
"INET_SOCK_V6_RADDR_A": fmt.Sprintf("+%d(+%d", 32, off),
"INET_SOCK_V6_RADDR_B": fmt.Sprintf("+%d(+%d", 40, off),
"INET_SOCK_V6_LADDR_A": fmt.Sprintf("+%d(+%d", 16, off),
"INET_SOCK_V6_LADDR_B": fmt.Sprintf("+%d(+%d", 24, off),
"INET_SOCK_V6_LIMIT": off,
}, true
}

Expand Down
9 changes: 6 additions & 3 deletions x-pack/auditbeat/module/system/socket/guess/inetsockaf.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (g *guessInetSockFamily) Provides() []string {
func (g *guessInetSockFamily) Requires() []string {
return []string{
"SOCKET_SOCK",
"INET_SOCK_V6_RADDR_A",
"INET_SOCK_V6_LIMIT",
"P1",
}
}
Expand All @@ -97,8 +97,11 @@ func (g *guessInetSockFamily) Probes() ([]helper.ProbeDef, error) {
func (g *guessInetSockFamily) Prepare(ctx Context) error {
g.ctx = ctx
var ok bool
if g.limit, ok = g.ctx.Vars["INET_SOCK_V6_RADDR_A"].(int); !ok {
return errors.New("required variable INET_SOCK_V6_RADDR_A not found")
// limit is used as a reference point within struct sock_common to know where
// to stop looking for the skc_family field, as limit is part of the fields
// in inet_sock that are past struct sock.
if g.limit, ok = g.ctx.Vars["INET_SOCK_V6_LIMIT"].(int); !ok {
return errors.New("required variable INET_SOCK_V6_LIMIT not found")
}
return nil
}
Expand Down
Loading

0 comments on commit 8604f0b

Please sign in to comment.