Skip to content

Commit

Permalink
tetragon: improve container process detection
Browse files Browse the repository at this point in the history
Any process that is running in host mount namespace is not a container,
containers by definition must mount/remount their / root hierarchy
inside their own mount namspace.

Signed-off-by: Djalal Harouni <[email protected]>
  • Loading branch information
tixxdz committed Oct 16, 2023
1 parent 09a10b3 commit e641ffc
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 49 deletions.
3 changes: 0 additions & 3 deletions bpf/lib/bpf_cgroup.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@
#define CGROUP2_SUPER_MAGIC 0x63677270 /* Cgroupv2 pseudo FS */
#endif

/* Our kernfs node name length, can be made 256? */
#define KN_NAME_LENGTH 128

/* Max nested cgroups that are tracked. Arbitrary value, nested cgroups
* that are at a level greater than 32 will be attached to the cgroup
* at level 32.
Expand Down
9 changes: 5 additions & 4 deletions bpf/lib/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,13 @@

#define EVENT_COMMON_FLAG_CLONE 0x01

/* Docker IDs are unique at first 12 characters, but we want to get
* 12chars plus any extra prefix used by the container environment.
/* Kernfs node name length for now it is 128. This is to handle
* container IDs that are unique at first 12 characters, but we want
* to get 12chars plus any extra prefix used by the container environment.
* Minikube for example prepends 'docker-' to the id. So lets copy
* 32B and assume at least 12B of it is ID info.
*/
#define DOCKER_ID_LENGTH 128
#define KN_NAME_LENGTH 128

struct msg_execve_key {
__u32 pid; // Process TGID
Expand Down Expand Up @@ -267,7 +268,7 @@ struct msg_k8s {
__u32 net_ns;
__u32 cid;
__u64 cgrpid;
char docker_id[DOCKER_ID_LENGTH];
char cgroup_name[KN_NAME_LENGTH]; /* At user space we determin if a container */
}; // All fields aligned so no 'packed' attribute.

struct msg_execve_event {
Expand Down
2 changes: 1 addition & 1 deletion bpf/process/bpf_process_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ __event_get_current_cgroup_name(struct cgroup *cgrp,

name = get_cgroup_name(cgrp);
if (name)
probe_read_str(msg->kube.docker_id, KN_NAME_LENGTH, name);
probe_read_str(msg->kube.cgroup_name, KN_NAME_LENGTH, name);
else
process->flags |= EVENT_ERROR_CGROUP_NAME;
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/api/processapi/processapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ type MsgCommon struct {
}

type MsgK8s struct {
NetNS uint32
Cid uint32
Cgrpid uint64
Docker [DOCKER_ID_LENGTH]byte
NetNS uint32
Cid uint32
Cgrpid uint64
CgroupName [DOCKER_ID_LENGTH]byte
}

type MsgK8sUnix struct {
Expand Down
11 changes: 10 additions & 1 deletion pkg/reader/namespace/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import (
"github.com/cilium/tetragon/pkg/option"
)

var hostNamespace *tetragon.Namespaces
var (
// host mount namespace to determin if process exec happens in host
HostMntInum uint32
hostNamespace *tetragon.Namespaces
)

func GetPidNsInode(pid uint32, nsStr string) uint32 {
pidStr := strconv.Itoa(int(pid))
Expand Down Expand Up @@ -198,6 +202,11 @@ func GetHostNamespace() *tetragon.Namespaces {
User: createHostNs("user"),
}
}

if HostMntInum == 0 {
HostMntInum = hostNamespace.Mnt.Inum
}

return hostNamespace
}

Expand Down
61 changes: 38 additions & 23 deletions pkg/sensors/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/cilium/tetragon/pkg/logger"
"github.com/cilium/tetragon/pkg/observer"
"github.com/cilium/tetragon/pkg/process"
"github.com/cilium/tetragon/pkg/reader/namespace"
"github.com/cilium/tetragon/pkg/sensors"
"github.com/cilium/tetragon/pkg/sensors/exec/procevents"
"github.com/cilium/tetragon/pkg/sensors/program"
Expand Down Expand Up @@ -55,36 +56,50 @@ func msgToExecveKubeUnix(m *processapi.MsgExecveEvent, exec_id string, filename
Cgrpid: m.Kube.Cgrpid,
}

// The first byte is set to zero if there is no docker ID for this event.
if m.Kube.Docker[0] != 0x00 {
// We always get a null terminated buffer from bpf
cgroup := cgroups.CgroupNameFromCStr(m.Kube.Docker[:processapi.CGROUP_NAME_LENGTH])
docker, _ := procevents.LookupContainerId(cgroup, true, false)
if docker != "" {
kube.Docker = docker
logger.GetLogger().WithFields(logrus.Fields{
"cgroup.id": kube.Cgrpid,
"cgroup.name": cgroup,
"docker": kube.Docker,
"process.exec_id": exec_id,
"process.binary": filename,
}).Trace("process_exec: container ID set successfully")
} else {
logger.GetLogger().WithFields(logrus.Fields{
"cgroup.id": kube.Cgrpid,
"cgroup.name": cgroup,
"process.exec_id": exec_id,
"process.binary": filename,
}).Trace("process_exec: no container ID due to cgroup name not being a compatible ID, ignoring.")
}
} else {
// The first byte is set to zero if there is no cgroup name for this event.
if m.Kube.CgroupName[0] == 0x00 {
logger.GetLogger().WithFields(logrus.Fields{
"cgroup.id": kube.Cgrpid,
"process.exec_id": exec_id,
"process.binary": filename,
}).Trace("process_exec: no container ID due to cgroup name being empty, ignoring.")
return kube
}

// We always get a null terminated buffer from bpf
cgroup := cgroups.CgroupNameFromCStr(m.Kube.CgroupName[:processapi.CGROUP_NAME_LENGTH])

// If in host namespace then this is not a container
if m.Namespaces.MntInum == namespace.HostMntInum {
logger.GetLogger().WithFields(logrus.Fields{
"cgroup.id": kube.Cgrpid,
"cgroup.name": cgroup,
"process.exec_id": exec_id,
"process.binary": filename,
}).Trace("process_exec: not a container as it runs in host mount namespace")
return kube
}

docker, _ := procevents.LookupContainerId(cgroup, true, false)
if docker == "" {
logger.GetLogger().WithFields(logrus.Fields{
"cgroup.id": kube.Cgrpid,
"cgroup.name": cgroup,
"process.exec_id": exec_id,
"process.binary": filename,
}).Trace("process_exec: no container ID due to cgroup name not being a compatible ID, ignoring.")
return kube
}

kube.Docker = docker
logger.GetLogger().WithFields(logrus.Fields{
"cgroup.id": kube.Cgrpid,
"cgroup.name": cgroup,
"docker": kube.Docker,
"process.exec_id": exec_id,
"process.binary": filename,
}).Trace("process_exec: container ID set successfully")

return kube
}

Expand Down
26 changes: 13 additions & 13 deletions pkg/sensors/exec/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,71 +56,71 @@ func Test_msgToExecveKubeUnix(t *testing.T) {
// Minikube has "docker-" prefix.
prefix := "docker-"
minikubeID := prefix + "9e123a99b140a6ea4a8d15040ca2c8ee2d5ee9605e81d66ae4e3e29c3f0ef220.scope"
copy(event.Kube.Docker[:], minikubeID)
copy(event.Kube.CgroupName[:], minikubeID)
kube := msgToExecveKubeUnix(&event, "", "")
assert.Equal(t, strings.Split(minikubeID, "-")[1][:idLength], kube.Docker)
event.Kube.Docker[0] = 0
event.Kube.CgroupName[0] = 0
kube = msgToExecveKubeUnix(&event, "", "")
assert.Empty(t, kube.Docker)

// GKE doesn't.
gkeID := "82836ef3675020258bee5075ace6264b3bc5300e20c975543cbc984bea59638f"
copy(event.Kube.Docker[:], gkeID)
copy(event.Kube.CgroupName[:], gkeID)
kube = msgToExecveKubeUnix(&event, "", "")
assert.Equal(t, gkeID[:idLength], kube.Docker)
assert.Equal(t, idLength, len(kube.Docker))
event.Kube.Docker[0] = 0
event.Kube.CgroupName[0] = 0
kube = msgToExecveKubeUnix(&event, "", "")
assert.Empty(t, kube.Docker)

id := "kubepods-burstable-pod29349498_197c_4919_b13f_9a928e7d001b.slice:cri-containerd:0ca2b3cd20e5f55a2bbe8d4aa3f811cf7963b40f0542ad147054b0fcb60fc400"
copy(event.Kube.Docker[:], id)
copy(event.Kube.CgroupName[:], id)
kube = msgToExecveKubeUnix(&event, "", "")
assert.Equal(t, id[80:80+idLength], kube.Docker)
assert.Equal(t, strings.Split(id, ":")[2][:idLength], kube.Docker)
assert.Equal(t, idLength, len(kube.Docker))

id = "kubepods-besteffort-pod13cb8437-00ed-40e4-99d8-e17193a58086.slice:cri-containerd:a5a6a3af5d51ad95b915ca948710b90a94abc279e84963b9d22a39f342ce67d9"
copy(event.Kube.Docker[:], id)
copy(event.Kube.CgroupName[:], id)
kube = msgToExecveKubeUnix(&event, "", "")
assert.Equal(t, id[81:81+idLength], kube.Docker)
assert.Equal(t, strings.Split(id, ":")[2][:idLength], kube.Docker)
assert.Equal(t, idLength, len(kube.Docker))

id = "cri-containerd-5694f82f44168cc048e014ae14d1b0c8ef673bec49f329dc169911ea638f63c2.scope"
copy(event.Kube.Docker[:], id)
copy(event.Kube.CgroupName[:], id)
kube = msgToExecveKubeUnix(&event, "", "")
assert.Equal(t, strings.Split(id, "-")[2][:idLength], kube.Docker)
assert.Equal(t, idLength, len(kube.Docker))

id = "libpod-01f3c60cfaadbb51e4d5947dd2ef0480d53551cbcee8f3ada8c3723b2bf03bf4"
copy(event.Kube.Docker[:], id)
copy(event.Kube.CgroupName[:], id)
kube = msgToExecveKubeUnix(&event, "", "")
assert.Equal(t, strings.Split(id, "-")[1][:idLength], kube.Docker)
assert.Equal(t, idLength, len(kube.Docker))

id = ":a5a6a3af5d51ad95b915ca948710b90a94abc279e84963b9d22a39f342ce67d9"
copy(event.Kube.Docker[:], id)
copy(event.Kube.CgroupName[:], id)
kube = msgToExecveKubeUnix(&event, "", "")
assert.Equal(t, strings.Split(id, ":")[1][:idLength], kube.Docker)
assert.Equal(t, idLength, len(kube.Docker))

// Empty event so we don't fail tests
for i := 0; i < processapi.DOCKER_ID_LENGTH; i++ {
event.Kube.Docker[i] = 0
event.Kube.CgroupName[i] = 0
}
// Not valid
id = "ba4c34f800cf9f92881fd55cea8e60d"
copy(event.Kube.Docker[:], id)
copy(event.Kube.CgroupName[:], id)
kube = msgToExecveKubeUnix(&event, "", "")
assert.Empty(t, kube.Docker)

// Empty event so we don't fail tests
for i := 0; i < processapi.DOCKER_ID_LENGTH; i++ {
event.Kube.Docker[i] = 0
event.Kube.CgroupName[i] = 0
}
id = ":ba4c34f800cf9f92881fd55cea8e60d"
copy(event.Kube.Docker[:], id)
copy(event.Kube.CgroupName[:], id)
kube = msgToExecveKubeUnix(&event, "", "")
assert.Empty(t, kube.Docker)
}
Expand Down

0 comments on commit e641ffc

Please sign in to comment.