Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Socket dataset: Workaround for bogus dereference in kernel 5.x #15771

Merged
merged 4 commits into from
Jan 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d

*Affecting all Beats*

TLS or Beats that accept connections over TLS and validate client certificates. {pull}14146[14146]
- TLS or Beats that accept connections over TLS and validate client certificates. {pull}14146[14146]
- Fix panic in the Logstash output when trying to send events to closed connection. {pull}15568[15568]

*Auditbeat*

- system/socket: Fixed compatibility issue with kernel 5.x. {pull}15771[15771]

*Filebeat*

Expand Down
16 changes: 10 additions & 6 deletions x-pack/auditbeat/module/system/socket/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -534,14 +534,16 @@ type udpSendMsgCall struct {
LPort uint16 `kprobe:"lport"`
RPort uint16 `kprobe:"rport"`
AltRPort uint16 `kprobe:"altrport"`
// SIPtr is the struct sockaddr_in pointer.
SIPtr uintptr `kprobe:"siptr"`
// SIAF is the address family in (struct sockaddr_in*)->sin_family.
SIAF uint16 `kprobe:"siaf"`
}

func (e *udpSendMsgCall) asFlow() flow {
raddr, rport := e.RAddr, e.RPort
if raddr == 0 {
if e.SIPtr == 0 || e.SIAF != unix.AF_INET {
raddr = e.AltRAddr
}
if rport == 0 {
rport = e.AltRPort
}
return flow{
Expand Down Expand Up @@ -586,14 +588,16 @@ type udpv6SendMsgCall struct {
LPort uint16 `kprobe:"lport"`
RPort uint16 `kprobe:"rport"`
AltRPort uint16 `kprobe:"altrport"`
// SI6Ptr is the struct sockaddr_in6 pointer.
SI6Ptr uintptr `kprobe:"si6ptr"`
// Si6AF is the address family field ((struct sockaddr_in6*)->sin6_family)
SI6AF uint16 `kprobe:"si6af"`
}

func (e *udpv6SendMsgCall) asFlow() flow {
raddra, raddrb, rport := e.RAddrA, e.RAddrB, e.RPort
if raddra == 0 && raddrb == 0 {
if e.SI6Ptr == 0 || e.SI6AF != unix.AF_INET6 {
raddra, raddrb = e.AltRAddrA, e.AltRAddrB
}
if rport == 0 {
rport = e.AltRPort
}
return flow{
Expand Down
139 changes: 139 additions & 0 deletions x-pack/auditbeat/module/system/socket/guess/deref.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

// +build linux,386 linux,amd64

package guess

import (
"encoding/hex"
"os"
"strconv"
"syscall"

"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/x-pack/auditbeat/module/system/socket/helper"
"github.com/elastic/beats/x-pack/auditbeat/tracing"
)

/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty comment block.

This is not an actual guess but a helper to check if the kernel kprobe
subsystem returns garbage after dereferencing a null pointer.

This code is run when the AUDITBEAT_SYSTEM_SOCKET_CHECK_DEREF environment
variable is set to a value greater than 0. When set, it will run the given
number of times, print the hexdump to the debug logs if non-zero memory is
found and set the NULL_PTR_DEREF_IS_OK (bool) variable.
*/

func init() {
if err := Registry.AddGuess(&guessDeref{}); err != nil {
panic(err)
}
}

const (
flagName = "NULL_PTR_DEREF_IS_OK"
envVar = "AUDITBEAT_SYSTEM_SOCKET_CHECK_DEREF"
)

type guessDeref struct {
ctx Context
tries int
garbage bool
}

// Condition allows the guess to run if the environment variable is set to a
// decimal value greater than zero.
func (g *guessDeref) Condition(ctx Context) (run bool, err error) {
v := os.Getenv(envVar)
if v == "" {
return false, nil
}
if g.tries, err = strconv.Atoi(v); err != nil || g.tries <= 0 {
return false, nil
}
return true, nil
}

// Name of this guess.
func (g *guessDeref) Name() string {
return "guess_deref"
}

// Provides returns the names of discovered variables.
func (g *guessDeref) Provides() []string {
return []string{
flagName,
}
}

// Requires declares the variables required to run this guess.
func (g *guessDeref) Requires() []string {
return []string{
"SYS_UNAME",
}
}

// Probes returns a kprobe on uname() that dumps the first bytes
// pointed to by its first parameter.
func (g *guessDeref) Probes() ([]helper.ProbeDef, error) {
return []helper.ProbeDef{
{
Probe: tracing.Probe{
Type: tracing.TypeKProbe,
Name: "guess_null_ptr_deref",
Address: "{{.SYS_UNAME}}",
Fetchargs: helper.MakeMemoryDump("{{.P1}}", 0, credDumpBytes),
},
Decoder: tracing.NewDumpDecoder,
},
}, nil
}

// Prepare is a no-op.
func (g *guessDeref) Prepare(ctx Context) error {
g.ctx = ctx
return nil
}

// Terminate is a no-op.
func (g *guessDeref) Terminate() error {
return nil
}

// MaxRepeats returns the configured number of repeats.
func (g *guessDeref) MaxRepeats() int {
return g.tries
}

// Extract receives the memory read through a null pointer and checks if it's
// zero or garbage.
func (g *guessDeref) Extract(ev interface{}) (common.MapStr, bool) {
raw := ev.([]byte)
if len(raw) != credDumpBytes {
return nil, false
}
for _, val := range raw {
if val != 0 {
g.ctx.Log.Errorf("Found non-zero memory:\n%s", hex.Dump(raw))
g.garbage = true
break
}
}
// Repeat until completed all tries
if g.tries--; g.tries > 0 {
return nil, true
}
return common.MapStr{
flagName: !g.garbage,
}, true
}

// Trigger invokes the uname syscall with a null parameter.
func (g *guessDeref) Trigger() error {
var ptr *syscall.Utsname
syscall.Uname(ptr)
return nil
}
4 changes: 2 additions & 2 deletions x-pack/auditbeat/module/system/socket/kprobes.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ var sharedKProbes = []helper.ProbeDef{
Probe: tracing.Probe{
Name: "udp_sendmsg_in",
Address: "udp_sendmsg",
Fetchargs: "sock={{.UDP_SENDMSG_SOCK}} size={{.UDP_SENDMSG_LEN}} laddr=+{{.INET_SOCK_LADDR}}({{.UDP_SENDMSG_SOCK}}):u32 lport=+{{.INET_SOCK_LPORT}}({{.UDP_SENDMSG_SOCK}}):u16 raddr=+{{.SOCKADDR_IN_ADDR}}(+0({{.UDP_SENDMSG_MSG}})):u32 rport=+{{.SOCKADDR_IN_PORT}}(+0({{.UDP_SENDMSG_MSG}})):u16 altraddr=+{{.INET_SOCK_RADDR}}({{.UDP_SENDMSG_SOCK}}):u32 altrport=+{{.INET_SOCK_RPORT}}({{.UDP_SENDMSG_SOCK}}):u16",
Fetchargs: "sock={{.UDP_SENDMSG_SOCK}} size={{.UDP_SENDMSG_LEN}} laddr=+{{.INET_SOCK_LADDR}}({{.UDP_SENDMSG_SOCK}}):u32 lport=+{{.INET_SOCK_LPORT}}({{.UDP_SENDMSG_SOCK}}):u16 raddr=+{{.SOCKADDR_IN_ADDR}}(+0({{.UDP_SENDMSG_MSG}})):u32 siptr=+0({{.UDP_SENDMSG_MSG}}) siaf=+{{.SOCKADDR_IN_AF}}(+0({{.UDP_SENDMSG_MSG}})):u16 rport=+{{.SOCKADDR_IN_PORT}}(+0({{.UDP_SENDMSG_MSG}})):u16 altraddr=+{{.INET_SOCK_RADDR}}({{.UDP_SENDMSG_SOCK}}):u32 altrport=+{{.INET_SOCK_RPORT}}({{.UDP_SENDMSG_SOCK}}):u16",
},
Decoder: helper.NewStructDecoder(func() interface{} { return new(udpSendMsgCall) }),
},
Expand Down Expand Up @@ -455,7 +455,7 @@ var ipv6KProbes = []helper.ProbeDef{
Probe: tracing.Probe{
Name: "udpv6_sendmsg_in",
Address: "udpv6_sendmsg",
Fetchargs: "sock={{.UDP_SENDMSG_SOCK}} size={{.UDP_SENDMSG_LEN}} laddra={{.INET_SOCK_V6_LADDR_A}}({{.UDP_SENDMSG_SOCK}}){{.INET_SOCK_V6_TERM}} laddrb={{.INET_SOCK_V6_LADDR_B}}({{.UDP_SENDMSG_SOCK}}){{.INET_SOCK_V6_TERM}} lport=+{{.INET_SOCK_LPORT}}({{.UDP_SENDMSG_SOCK}}):u16 raddra=+{{.SOCKADDR_IN6_ADDRA}}(+0({{.UDP_SENDMSG_MSG}})):u64 raddrb=+{{.SOCKADDR_IN6_ADDRB}}(+0({{.UDP_SENDMSG_MSG}})):u64 rport=+{{.SOCKADDR_IN6_PORT}}(+0({{.UDP_SENDMSG_MSG}})):u16 altraddra={{.INET_SOCK_V6_RADDR_A}}({{.UDP_SENDMSG_SOCK}}){{.INET_SOCK_V6_TERM}} altraddrb={{.INET_SOCK_V6_RADDR_B}}({{.UDP_SENDMSG_SOCK}}){{.INET_SOCK_V6_TERM}} altrport=+{{.INET_SOCK_RPORT}}({{.UDP_SENDMSG_SOCK}}):u16",
Fetchargs: "sock={{.UDP_SENDMSG_SOCK}} size={{.UDP_SENDMSG_LEN}} laddra={{.INET_SOCK_V6_LADDR_A}}({{.UDP_SENDMSG_SOCK}}){{.INET_SOCK_V6_TERM}} laddrb={{.INET_SOCK_V6_LADDR_B}}({{.UDP_SENDMSG_SOCK}}){{.INET_SOCK_V6_TERM}} lport=+{{.INET_SOCK_LPORT}}({{.UDP_SENDMSG_SOCK}}):u16 raddra=+{{.SOCKADDR_IN6_ADDRA}}(+0({{.UDP_SENDMSG_MSG}})):u64 raddrb=+{{.SOCKADDR_IN6_ADDRB}}(+0({{.UDP_SENDMSG_MSG}})):u64 rport=+{{.SOCKADDR_IN6_PORT}}(+0({{.UDP_SENDMSG_MSG}})):u16 altraddra={{.INET_SOCK_V6_RADDR_A}}({{.UDP_SENDMSG_SOCK}}){{.INET_SOCK_V6_TERM}} altraddrb={{.INET_SOCK_V6_RADDR_B}}({{.UDP_SENDMSG_SOCK}}){{.INET_SOCK_V6_TERM}} altrport=+{{.INET_SOCK_RPORT}}({{.UDP_SENDMSG_SOCK}}):u16 si6ptr=+0({{.UDP_SENDMSG_MSG}}) si6af=+{{.SOCKADDR_IN6_AF}}(+0({{.UDP_SENDMSG_MSG}})):u16",
},
Decoder: helper.NewStructDecoder(func() interface{} { return new(udpv6SendMsgCall) }),
},
Expand Down
74 changes: 70 additions & 4 deletions x-pack/auditbeat/module/system/socket/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/joeshaw/multierror"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"golang.org/x/sys/unix"

"github.com/elastic/beats/libbeat/beat"
"github.com/elastic/beats/x-pack/auditbeat/module/system/socket/dns"
Expand Down Expand Up @@ -148,11 +149,9 @@ func TestUDPOutgoingSinglePacketWithProcess(t *testing.T) {
Sock: sock,
Size: 123,
LAddr: lAddr,
RAddr: rAddr,
AltRAddr: 0,
AltRAddr: rAddr,
LPort: lPort,
RPort: rPort,
AltRPort: 0,
AltRPort: rPort,
},
&inetReleaseCall{Meta: meta(1234, 1235, 17), Sock: sock},
&doExit{Meta: meta(1234, 1234, 18)},
Expand Down Expand Up @@ -293,6 +292,14 @@ func ipv4(ip string) uint32 {
return tracing.MachineEndian.Uint32(netIP)
}

func ipv6(ip string) (hi uint64, lo uint64) {
netIP := net.ParseIP(ip).To16()
if netIP == nil {
panic("bad ip")
}
return tracing.MachineEndian.Uint64(netIP[:]), tracing.MachineEndian.Uint64(netIP[8:])
}

func feedEvents(evs []event, st *state, t *testing.T) error {
for idx, ev := range evs {
t.Logf("Delivering event %d: %s", idx, ev.String())
Expand Down Expand Up @@ -515,3 +522,62 @@ func TestDNSTracker(t *testing.T) {
}.Run(t)
})
}

func TestUDPSendMsgAltLogic(t *testing.T) {
const expectedIPv4 = "6 probe=0 pid=1234 tid=1235 udp_sendmsg(sock=0x0, size=0, 10.11.12.13:1010 -> 10.20.30.40:1234)"
const expectedIPv6 = "6 probe=0 pid=1234 tid=1235 udpv6_sendmsg(sock=0x0, size=0, [fddd::bebe]:1010 -> [fddd::cafe]:1234)"
t.Run("ipv4 non-connected", func(t *testing.T) {
ev := udpSendMsgCall{
Meta: meta(1234, 1235, 6),
LAddr: ipv4("10.11.12.13"),
LPort: be16(1010),
RAddr: ipv4("10.20.30.40"),
RPort: be16(1234),
AltRAddr: ipv4("192.168.255.255"),
AltRPort: be16(555),
SIPtr: 0x7fffffff,
SIAF: unix.AF_INET,
}
assert.Equal(t, expectedIPv4, ev.String())
})
t.Run("ipv4 connected", func(t *testing.T) {
ev := udpSendMsgCall{
Meta: meta(1234, 1235, 6),
LAddr: ipv4("10.11.12.13"),
LPort: be16(1010),
RAddr: ipv4("192.168.255.255"),
RPort: be16(555),
AltRAddr: ipv4("10.20.30.40"),
AltRPort: be16(1234),
}
assert.Equal(t, expectedIPv4, ev.String())
})
t.Run("ipv6 non-connected", func(t *testing.T) {
ev := udpv6SendMsgCall{
Meta: meta(1234, 1235, 6),
LPort: be16(1010),
RPort: be16(1234),
AltRPort: be16(555),
SI6Ptr: 0x7fffffff,
SI6AF: unix.AF_INET6,
}
ev.LAddrA, ev.LAddrB = ipv6("fddd::bebe")
ev.RAddrA, ev.RAddrB = ipv6("fddd::cafe")
ev.AltRAddrA, ev.AltRAddrB = ipv6("fddd::bad:bad")
assert.Equal(t, expectedIPv6, ev.String())
})

t.Run("ipv6 connected", func(t *testing.T) {
ev := udpv6SendMsgCall{
Meta: meta(1234, 1235, 6),
LPort: be16(1010),
RPort: be16(555),
AltRPort: be16(1234),
}
ev.LAddrA, ev.LAddrB = ipv6("fddd::bebe")
ev.RAddrA, ev.RAddrB = ipv6("fddd::bad:bad")
ev.AltRAddrA, ev.AltRAddrB = ipv6("fddd::cafe")
assert.Equal(t, expectedIPv6, ev.String())
})

}