diff --git a/pkg/sentry/kernel/BUILD b/pkg/sentry/kernel/BUILD index e313d69fa..0d3093688 100644 --- a/pkg/sentry/kernel/BUILD +++ b/pkg/sentry/kernel/BUILD @@ -165,6 +165,7 @@ go_library( "ptrace_amd64.go", "ptrace_arm64.go", "rseq.go", + "seccheck.go", "seccomp.go", "seqatomic_taskgoroutineschedinfo_unsafe.go", "session_list.go", @@ -272,6 +273,7 @@ go_library( "//pkg/sentry/pgalloc", "//pkg/sentry/platform", "//pkg/sentry/seccheck", + "//pkg/sentry/seccheck/points:points_go_proto", "//pkg/sentry/socket/netlink/port", "//pkg/sentry/socket/unix/transport", "//pkg/sentry/time", diff --git a/pkg/sentry/kernel/auth/BUILD b/pkg/sentry/kernel/auth/BUILD index 9aa03f506..73c906136 100644 --- a/pkg/sentry/kernel/auth/BUILD +++ b/pkg/sentry/kernel/auth/BUILD @@ -65,6 +65,8 @@ go_library( "//pkg/context", "//pkg/errors/linuxerr", "//pkg/log", + "//pkg/sentry/seccheck", + "//pkg/sentry/seccheck/points:points_go_proto", "//pkg/sync", ], ) diff --git a/pkg/sentry/kernel/auth/credentials.go b/pkg/sentry/kernel/auth/credentials.go index fc245c54b..25ffb98ac 100644 --- a/pkg/sentry/kernel/auth/credentials.go +++ b/pkg/sentry/kernel/auth/credentials.go @@ -17,6 +17,8 @@ package auth import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/sentry/seccheck" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" ) // Credentials contains information required to authorize privileged operations @@ -260,3 +262,17 @@ func (c *Credentials) SetGID(gid GID) error { c.SavedKGID = kgid return nil } + +// LoadSeccheckData sets credential data based on mask. +func (c *Credentials) LoadSeccheckData(mask seccheck.FieldMask, info *pb.ContextData) { + if mask.Contains(seccheck.FieldCtxtCredentials) { + info.Credentials = &pb.Credentials{ + RealUid: uint32(c.RealKUID), + EffectiveUid: uint32(c.EffectiveKUID), + SavedUid: uint32(c.SavedKUID), + RealGid: uint32(c.RealKGID), + EffectiveGid: uint32(c.EffectiveKGID), + SavedGid: uint32(c.SavedKGID), + } + } +} diff --git a/pkg/sentry/kernel/seccheck.go b/pkg/sentry/kernel/seccheck.go new file mode 100644 index 000000000..91e17697b --- /dev/null +++ b/pkg/sentry/kernel/seccheck.go @@ -0,0 +1,67 @@ +// Copyright 2022 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kernel + +import ( + "gvisor.dev/gvisor/pkg/sentry/seccheck" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" +) + +// LoadSeccheckData sets info from the task based on mask. +func LoadSeccheckData(t *Task, mask seccheck.FieldMask, info *pb.ContextData) { + t.k.tasks.mu.RLock() + defer t.k.tasks.mu.RUnlock() + LoadSeccheckDataLocked(t, mask, info) +} + +// LoadSeccheckDataLocked sets info from the task based on mask. +// +// Preconditions: The TaskSet mutex must be locked. +func LoadSeccheckDataLocked(t *Task, mask seccheck.FieldMask, info *pb.ContextData) { + if mask.Contains(seccheck.FieldCtxtTime) { + info.TimeNs = t.k.RealtimeClock().Now().Nanoseconds() + } + + if t == nil { + return + } + if mask.Contains(seccheck.FieldCtxtThreadID) { + info.ThreadId = int32(t.k.tasks.Root.tids[t]) + } + if mask.Contains(seccheck.FieldCtxtThreadStartTime) { + info.ThreadStartTimeNs = t.startTime.Nanoseconds() + } + if mask.Contains(seccheck.FieldCtxtThreadGroupID) { + info.ThreadGroupId = int32(t.k.tasks.Root.tgids[t.tg]) + } + if mask.Contains(seccheck.FieldCtxtThreadGroupStartTime) { + info.ThreadGroupStartTimeNs = t.tg.leader.startTime.Nanoseconds() + } + if mask.Contains(seccheck.FieldCtxtContainerID) { + info.ContainerId = t.tg.leader.ContainerID() + } + if mask.Contains(seccheck.FieldCtxtCwd) { + root := t.FSContext().RootDirectoryVFS2() + defer root.DecRef(t) + wd := t.FSContext().WorkingDirectoryVFS2() + defer wd.DecRef(t) + vfsObj := root.Mount().Filesystem().VirtualFilesystem() + info.Cwd, _ = vfsObj.PathnameWithDeleted(t, root, wd) + } + if mask.Contains(seccheck.FieldCtxtProcessName) { + info.ProcessName = t.Name() + } + t.Credentials().LoadSeccheckData(mask, info) +} diff --git a/pkg/sentry/kernel/task.go b/pkg/sentry/kernel/task.go index df682548b..fcaf802b2 100644 --- a/pkg/sentry/kernel/task.go +++ b/pkg/sentry/kernel/task.go @@ -31,7 +31,6 @@ import ( "gvisor.dev/gvisor/pkg/sentry/kernel/sched" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" "gvisor.dev/gvisor/pkg/sentry/platform" - "gvisor.dev/gvisor/pkg/sentry/seccheck" "gvisor.dev/gvisor/pkg/sentry/usage" "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/sync" @@ -880,23 +879,3 @@ func (t *Task) ResetKcov() { t.kcov = nil } } - -// Preconditions: The TaskSet mutex must be locked. -func (t *Task) loadSeccheckInfoLocked(req seccheck.TaskFieldSet, mask *seccheck.TaskFieldSet, info *seccheck.TaskInfo) { - if req.Contains(seccheck.TaskFieldThreadID) { - info.ThreadID = int32(t.k.tasks.Root.tids[t]) - mask.Add(seccheck.TaskFieldThreadID) - } - if req.Contains(seccheck.TaskFieldThreadStartTime) { - info.ThreadStartTime = t.startTime - mask.Add(seccheck.TaskFieldThreadStartTime) - } - if req.Contains(seccheck.TaskFieldThreadGroupID) { - info.ThreadGroupID = int32(t.k.tasks.Root.tgids[t.tg]) - mask.Add(seccheck.TaskFieldThreadGroupID) - } - if req.Contains(seccheck.TaskFieldThreadGroupStartTime) { - info.ThreadGroupStartTime = t.tg.leader.startTime - mask.Add(seccheck.TaskFieldThreadGroupStartTime) - } -} diff --git a/pkg/sentry/kernel/task_clone.go b/pkg/sentry/kernel/task_clone.go index a84b88661..c824d6269 100644 --- a/pkg/sentry/kernel/task_clone.go +++ b/pkg/sentry/kernel/task_clone.go @@ -24,6 +24,7 @@ import ( "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/inet" "gvisor.dev/gvisor/pkg/sentry/seccheck" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" "gvisor.dev/gvisor/pkg/usermem" ) @@ -247,8 +248,8 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { defer nt.Start(tid) if seccheck.Global.Enabled(seccheck.PointClone) { - mask, info := getCloneSeccheckInfo(t, nt, args) - if err := seccheck.Global.Clone(t, mask, &info); err != nil { + mask, info := getCloneSeccheckInfo(t, nt) + if err := seccheck.Global.Clone(t, mask, info); err != nil { // nt has been visible to the rest of the system since NewTask, so // it may be blocking execve or a group stop, have been notified // for group signal delivery, had children reparented to it, etc. @@ -306,20 +307,23 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { return ntid, nil, nil } -func getCloneSeccheckInfo(t, nt *Task, args *linux.CloneArgs) (seccheck.CloneFieldSet, seccheck.CloneInfo) { - req := seccheck.Global.CloneReq() - info := seccheck.CloneInfo{ - Credentials: t.Credentials(), - Args: *args, - } - var mask seccheck.CloneFieldSet - mask.Add(seccheck.CloneFieldCredentials) - mask.Add(seccheck.CloneFieldArgs) +func getCloneSeccheckInfo(t, nt *Task) (seccheck.FieldSet, *pb.CloneInfo) { + fields := seccheck.Global.GetFieldSet(seccheck.PointClone) + t.k.tasks.mu.RLock() defer t.k.tasks.mu.RUnlock() - t.loadSeccheckInfoLocked(req.Invoker, &mask.Invoker, &info.Invoker) - nt.loadSeccheckInfoLocked(req.Created, &mask.Created, &info.Created) - return mask, info + info := &pb.CloneInfo{ + CreatedThreadId: int32(nt.k.tasks.Root.tids[nt]), + CreatedThreadGroupId: int32(nt.k.tasks.Root.tgids[nt.tg]), + CreatedThreadStartTimeNs: nt.startTime.Nanoseconds(), + } + + if !fields.Context.Empty() { + info.ContextData = &pb.ContextData{} + LoadSeccheckDataLocked(t, fields.Context, info.ContextData) + } + + return fields, info } // maybeBeginVforkStop checks if a previously-started vfork child is still diff --git a/pkg/sentry/kernel/task_exec.go b/pkg/sentry/kernel/task_exec.go index cadc3b848..0d74c2f21 100644 --- a/pkg/sentry/kernel/task_exec.go +++ b/pkg/sentry/kernel/task_exec.go @@ -69,9 +69,9 @@ import ( "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/fsbridge" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/mm" "gvisor.dev/gvisor/pkg/sentry/seccheck" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" "gvisor.dev/gvisor/pkg/sentry/vfs" ) @@ -97,7 +97,7 @@ func (t *Task) Execve(newImage *TaskImage, argv, env []string, executable fsbrid // We can't clearly hold kernel package locks while stat'ing executable. if seccheck.Global.Enabled(seccheck.PointExecve) { mask, info := getExecveSeccheckInfo(t, argv, env, executable, pathname) - if err := seccheck.Global.Execve(t, mask, &info); err != nil { + if err := seccheck.Global.Execve(t, mask, info); err != nil { newImage.release() return nil, err } @@ -302,44 +302,28 @@ func (t *Task) promoteLocked() { oldLeader.exitNotifyLocked(false) } -func getExecveSeccheckInfo(t *Task, argv, env []string, executable fsbridge.File, pathname string) (seccheck.ExecveFieldSet, seccheck.ExecveInfo) { - req := seccheck.Global.ExecveReq() - info := seccheck.ExecveInfo{ - Credentials: t.Credentials(), - Argv: argv, - Env: env, +func getExecveSeccheckInfo(t *Task, argv, env []string, executable fsbridge.File, pathname string) (seccheck.FieldSet, *pb.ExecveInfo) { + fields := seccheck.Global.GetFieldSet(seccheck.PointExecve) + info := &pb.ExecveInfo{ + Argv: argv, + Env: env, } - var mask seccheck.ExecveFieldSet - mask.Add(seccheck.ExecveFieldCredentials) - mask.Add(seccheck.ExecveFieldArgv) - mask.Add(seccheck.ExecveFieldEnv) if executable != nil { info.BinaryPath = pathname - mask.Add(seccheck.ExecveFieldBinaryPath) if vfs2bridgeFile, ok := executable.(*fsbridge.VFSFile); ok { - if req.Contains(seccheck.ExecveFieldBinaryMode) || req.Contains(seccheck.ExecveFieldBinaryUID) || req.Contains(seccheck.ExecveFieldBinaryGID) { - var statOpts vfs.StatOptions - if req.Contains(seccheck.ExecveFieldBinaryMode) { - statOpts.Mask |= linux.STATX_TYPE | linux.STATX_MODE - } - if req.Contains(seccheck.ExecveFieldBinaryUID) { - statOpts.Mask |= linux.STATX_UID - } - if req.Contains(seccheck.ExecveFieldBinaryGID) { - statOpts.Mask |= linux.STATX_GID + if fields.Local.Contains(seccheck.ExecveFieldBinaryInfo) { + statOpts := vfs.StatOptions{ + Mask: linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID, } if stat, err := vfs2bridgeFile.FileDescription().Stat(t, statOpts); err == nil { if stat.Mask&(linux.STATX_TYPE|linux.STATX_MODE) == (linux.STATX_TYPE | linux.STATX_MODE) { - info.BinaryMode = stat.Mode - mask.Add(seccheck.ExecveFieldBinaryMode) + info.BinaryMode = uint32(stat.Mode) } if stat.Mask&linux.STATX_UID != 0 { - info.BinaryUID = auth.KUID(stat.UID) - mask.Add(seccheck.ExecveFieldBinaryUID) + info.BinaryUid = stat.UID } if stat.Mask&linux.STATX_GID != 0 { - info.BinaryGID = auth.KGID(stat.GID) - mask.Add(seccheck.ExecveFieldBinaryGID) + info.BinaryGid = stat.GID } } } @@ -347,8 +331,10 @@ func getExecveSeccheckInfo(t *Task, argv, env []string, executable fsbridge.File // SHA256, which is very expensive. } } - t.k.tasks.mu.RLock() - defer t.k.tasks.mu.RUnlock() - t.loadSeccheckInfoLocked(req.Invoker, &mask.Invoker, &info.Invoker) - return mask, info + + if !fields.Context.Empty() { + info.ContextData = &pb.ContextData{} + LoadSeccheckData(t, fields.Context, info.ContextData) + } + return fields, info } diff --git a/pkg/sentry/kernel/task_exit.go b/pkg/sentry/kernel/task_exit.go index a74638711..c827914b5 100644 --- a/pkg/sentry/kernel/task_exit.go +++ b/pkg/sentry/kernel/task_exit.go @@ -33,6 +33,7 @@ import ( "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/seccheck" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" "gvisor.dev/gvisor/pkg/waiter" ) @@ -646,7 +647,7 @@ func (t *Task) exitNotifyLocked(fromPtraceDetach bool) { } if seccheck.Global.Enabled(seccheck.PointExitNotifyParent) { mask, info := getExitNotifyParentSeccheckInfo(t) - seccheck.Global.ExitNotifyParent(t, mask, &info) + seccheck.Global.ExitNotifyParent(t, mask, info) } } } @@ -697,15 +698,18 @@ func (t *Task) exitNotificationSignal(sig linux.Signal, receiver *Task) *linux.S } // Preconditions: The TaskSet mutex must be locked. -func getExitNotifyParentSeccheckInfo(t *Task) (seccheck.ExitNotifyParentFieldSet, seccheck.ExitNotifyParentInfo) { - req := seccheck.Global.ExitNotifyParentReq() - info := seccheck.ExitNotifyParentInfo{ - ExitStatus: t.tg.exitStatus, +func getExitNotifyParentSeccheckInfo(t *Task) (seccheck.FieldSet, *pb.ExitNotifyParentInfo) { + fields := seccheck.Global.GetFieldSet(seccheck.PointExitNotifyParent) + + info := &pb.ExitNotifyParentInfo{ + ExitStatus: int32(t.tg.exitStatus), } - var mask seccheck.ExitNotifyParentFieldSet - mask.Add(seccheck.ExitNotifyParentFieldExitStatus) - t.loadSeccheckInfoLocked(req.Exiter, &mask.Exiter, &info.Exiter) - return mask, info + if !fields.Context.Empty() { + info.ContextData = &pb.ContextData{} + LoadSeccheckDataLocked(t, fields.Context, info.ContextData) + } + + return fields, info } // ExitStatus returns t's exit status, which is only guaranteed to be diff --git a/pkg/sentry/seccheck/BUILD b/pkg/sentry/seccheck/BUILD index 35feb969f..f301bfdf1 100644 --- a/pkg/sentry/seccheck/BUILD +++ b/pkg/sentry/seccheck/BUILD @@ -1,21 +1,8 @@ load("//tools:defs.bzl", "go_library", "go_test") -load("//tools/go_fieldenum:defs.bzl", "go_fieldenum") load("//tools/go_generics:defs.bzl", "go_template_instance") licenses(["notice"]) -go_fieldenum( - name = "seccheck_fieldenum", - srcs = [ - "clone.go", - "execve.go", - "exit.go", - "task.go", - ], - out = "seccheck_fieldenum.go", - package = "seccheck", -) - go_template_instance( name = "seqatomic_checkerslice", out = "seqatomic_checkerslice_unsafe.go", @@ -34,17 +21,15 @@ go_library( "execve.go", "exit.go", "seccheck.go", - "seccheck_fieldenum.go", "seqatomic_checkerslice_unsafe.go", - "task.go", ], visibility = ["//:sandbox"], deps = [ "//pkg/abi/linux", "//pkg/context", "//pkg/gohacks", - "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/time", + "//pkg/sentry/seccheck/points:points_go_proto", "//pkg/sync", ], ) @@ -54,5 +39,8 @@ go_test( size = "small", srcs = ["seccheck_test.go"], library = ":seccheck", - deps = ["//pkg/context"], + deps = [ + "//pkg/context", + "//pkg/sentry/seccheck/points:points_go_proto", + ], ) diff --git a/pkg/sentry/seccheck/clone.go b/pkg/sentry/seccheck/clone.go index d635d8e59..9613e2c6f 100644 --- a/pkg/sentry/seccheck/clone.go +++ b/pkg/sentry/seccheck/clone.go @@ -15,37 +15,14 @@ package seccheck import ( - "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" ) -// CloneInfo contains information used by the Clone checkpoint. -// -// +fieldenum Clone -type CloneInfo struct { - // Invoker identifies the invoking thread. - Invoker TaskInfo - - // Credentials are the invoking thread's credentials. - Credentials *auth.Credentials - - // Args contains the arguments to kernel.Task.Clone(). - Args linux.CloneArgs - - // Created identifies the created thread. - Created TaskInfo -} - -// CloneReq returns fields required by the Clone checkpoint. -func (s *State) CloneReq() CloneFieldSet { - return s.cloneReq.Load() -} - // Clone is called at the Clone checkpoint. -func (s *State) Clone(ctx context.Context, mask CloneFieldSet, info *CloneInfo) error { +func (s *State) Clone(ctx context.Context, fields FieldSet, info *pb.CloneInfo) error { for _, c := range s.getCheckers() { - if err := c.Clone(ctx, mask, *info); err != nil { + if err := c.Clone(ctx, fields, info); err != nil { return err } } diff --git a/pkg/sentry/seccheck/execve.go b/pkg/sentry/seccheck/execve.go index ec9e0370b..83a40db59 100644 --- a/pkg/sentry/seccheck/execve.go +++ b/pkg/sentry/seccheck/execve.go @@ -16,54 +16,19 @@ package seccheck import ( "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" ) -// ExecveInfo contains information used by the Execve checkpoint. -// -// +fieldenum Execve -type ExecveInfo struct { - // Invoker identifies the invoking thread. - Invoker TaskInfo - - // Credentials are the invoking thread's credentials. - Credentials *auth.Credentials - - // BinaryPath is a path to the executable binary file being switched to in - // the mount namespace in which it was opened. - BinaryPath string - - // Argv is the new process image's argument vector. - Argv []string - - // Env is the new process image's environment variables. - Env []string - - // BinaryMode is the executable binary file's mode. - BinaryMode uint16 - - // BinaryUID is the executable binary file's owner. - BinaryUID auth.KUID - - // BinaryGID is the executable binary file's group. - BinaryGID auth.KGID - - // BinarySHA256 is the SHA-256 hash of the executable binary file. - // - // Note that this requires reading the entire file into memory, which is - // likely to be extremely slow. - BinarySHA256 [32]byte -} - -// ExecveReq returns fields required by the Execve checkpoint. -func (s *State) ExecveReq() ExecveFieldSet { - return s.execveReq.Load() -} +const ( + // ExecveFieldBinaryInfo is an optional field to collect information about the + // binary being executed. + ExecveFieldBinaryInfo Field = iota +) // Execve is called at the Execve checkpoint. -func (s *State) Execve(ctx context.Context, mask ExecveFieldSet, info *ExecveInfo) error { +func (s *State) Execve(ctx context.Context, mask FieldSet, info *pb.ExecveInfo) error { for _, c := range s.getCheckers() { - if err := c.Execve(ctx, mask, *info); err != nil { + if err := c.Execve(ctx, mask, info); err != nil { return err } } diff --git a/pkg/sentry/seccheck/exit.go b/pkg/sentry/seccheck/exit.go index 15bc5879b..7f28ce2ce 100644 --- a/pkg/sentry/seccheck/exit.go +++ b/pkg/sentry/seccheck/exit.go @@ -15,41 +15,19 @@ package seccheck import ( - "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" ) -// ExitNotifyParentInfo contains information used by the ExitNotifyParent -// checkpoint. -// -// +fieldenum ExitNotifyParent -type ExitNotifyParentInfo struct { - // Exiter identifies the exiting thread. Note that by the checkpoint's - // definition, Exiter.ThreadID == Exiter.ThreadGroupID and - // Exiter.ThreadStartTime == Exiter.ThreadGroupStartTime, so requesting - // ThreadGroup* fields is redundant. - Exiter TaskInfo - - // ExitStatus is the exiting thread group's exit status, as reported - // by wait*(). - ExitStatus linux.WaitStatus -} - -// ExitNotifyParentReq returns fields required by the ExitNotifyParent -// checkpoint. -func (s *State) ExitNotifyParentReq() ExitNotifyParentFieldSet { - return s.exitNotifyParentReq.Load() -} - // ExitNotifyParent is called at the ExitNotifyParent checkpoint. // // The ExitNotifyParent checkpoint occurs when a zombied thread group leader, // not waiting for exit acknowledgement from a non-parent ptracer, becomes the // last non-dead thread in its thread group and notifies its parent of its // exiting. -func (s *State) ExitNotifyParent(ctx context.Context, mask ExitNotifyParentFieldSet, info *ExitNotifyParentInfo) error { +func (s *State) ExitNotifyParent(ctx context.Context, fields FieldSet, info *pb.ExitNotifyParentInfo) error { for _, c := range s.getCheckers() { - if err := c.ExitNotifyParent(ctx, mask, *info); err != nil { + if err := c.ExitNotifyParent(ctx, fields, info); err != nil { return err } } diff --git a/pkg/sentry/seccheck/points/BUILD b/pkg/sentry/seccheck/points/BUILD new file mode 100644 index 000000000..0142b4b73 --- /dev/null +++ b/pkg/sentry/seccheck/points/BUILD @@ -0,0 +1,13 @@ +load("//tools:defs.bzl", "proto_library") + +licenses(["notice"]) + +package(default_visibility = ["//:sandbox"]) + +proto_library( + name = "points", + srcs = [ + "common.proto", + "sentry.proto", + ], +) diff --git a/pkg/sentry/seccheck/points/common.proto b/pkg/sentry/seccheck/points/common.proto new file mode 100644 index 000000000..b087f9619 --- /dev/null +++ b/pkg/sentry/seccheck/points/common.proto @@ -0,0 +1,47 @@ +// Copyright 2022 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package gvisor.common; + +message Credentials { + uint32 real_uid = 1; + uint32 effective_uid = 2; + uint32 saved_uid = 3; + + uint32 real_gid = 4; + uint32 effective_gid = 5; + uint32 saved_gid = 6; +} + +message ContextData { + int64 time_ns = 1; + + int32 thread_id = 2; + + int64 thread_start_time_ns = 3; + + int32 thread_group_id = 4; + + int64 thread_group_start_time_ns = 5; + + string container_id = 6; + + Credentials credentials = 7; + + string cwd = 8; + + string process_name = 9; +} diff --git a/pkg/sentry/seccheck/points/sentry.proto b/pkg/sentry/seccheck/points/sentry.proto new file mode 100644 index 000000000..8a4f21056 --- /dev/null +++ b/pkg/sentry/seccheck/points/sentry.proto @@ -0,0 +1,67 @@ +// Copyright 2022 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package gvisor.sentry; + +import "pkg/sentry/seccheck/points/common.proto"; + +// CloneInfo contains information used by the Clone checkpoint. +message CloneInfo { + gvisor.common.ContextData context_data = 1; + + // CreatedThreadID is the thread's ID in the root PID namespace. + int32 created_thread_id = 3; + + int32 created_thread_group_id = 4; + + // CreatedThreadStartTime is the thread's CLOCK_REALTIME start time. + int64 created_thread_start_time_ns = 5; +} + +// ExecveInfo contains information used by the Execve checkpoint. +message ExecveInfo { + gvisor.common.ContextData context_data = 1; + + // BinaryPath is a path to the executable binary file being switched to in + // the mount namespace in which it was opened. + string binary_path = 2; + + // Argv is the new process image's argument vector. + repeated string argv = 3; + + // Env is the new process image's environment variables. + repeated string env = 4; + + // BinaryMode is the executable binary file's mode. + uint32 binary_mode = 5; + + uint32 binary_uid = 6; + uint32 binary_gid = 7; + + // binary_sha256 is the SHA-256 hash of the executable binary file. + // + // Note that this requires reading the entire file into memory, which is + // likely to be extremely slow. + bytes binary_sha256 = 8; +} + +message ExitNotifyParentInfo { + gvisor.common.ContextData context_data = 1; + + // ExitStatus is the exiting thread group's exit status, as reported + // by wait*(). + int32 exit_status = 2; +} diff --git a/pkg/sentry/seccheck/seccheck.go b/pkg/sentry/seccheck/seccheck.go index 3156c103d..b64508ff5 100644 --- a/pkg/sentry/seccheck/seccheck.go +++ b/pkg/sentry/seccheck/seccheck.go @@ -20,6 +20,7 @@ import ( "sync/atomic" "gvisor.dev/gvisor/pkg/context" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" "gvisor.dev/gvisor/pkg/sync" ) @@ -37,6 +38,71 @@ const ( numPointBitmaskUint32s = (int(pointLength)-1)/32 + 1 ) +// FieldCtxtX represents a data field that comes from the Context. +const ( + FieldCtxtTime Field = iota + FieldCtxtThreadID + FieldCtxtThreadStartTime + FieldCtxtThreadGroupID + FieldCtxtThreadGroupStartTime + FieldCtxtContainerID + FieldCtxtCredentials + FieldCtxtCwd + FieldCtxtProcessName +) + +// FieldSet contains all optional fields to be collected by a given Point. +type FieldSet struct { + // Local indicates which optional fields from the Point that needs to be + // collected, e.g. resolving path from an FD, or collecting a large field. + Local FieldMask + + // Context indicates which optional fields from the Context that needs to be + // collected, e.g. PID, credentials, current time. + Context FieldMask +} + +// Field represents the index of a single optional field to be collect for a +// Point. +type Field uint + +// FieldMask is a bitmask with a single bit representing an optional field to be +// collected. The meaning of each bit varies per point. The mask is currently +// limited to 64 fields. If more are needed, FieldMask can be expanded to +// support additional fields. +type FieldMask struct { + mask uint64 +} + +// MakeFieldMask creates a FieldMask from a set of Fields. +func MakeFieldMask(fields ...Field) FieldMask { + var m FieldMask + for _, field := range fields { + m.Add(field) + } + return m +} + +// Contains returns true if the mask contains the Field. +func (fm *FieldMask) Contains(field Field) bool { + return fm.mask&(1<