mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Use proto structs for seccheck points
Given that in most cases points are serialized to another process, point data is now created diretly into protos. As part of this change, infrastructure to track optional and context fields was created to facilitate addition of lots of Points which is needed for upcomming of changes. Updates #4805 Currently the SST code is converting seccheck protos into SST protos in the sentry before sending it to the API. After this change, SST checker will be changed to send seccheck protos to the API and the API then converts these into SST on the way to pubsub. PiperOrigin-RevId: 442688320
This commit is contained in:
committed by
gVisor bot
parent
59c7cd5ddf
commit
8a24f200e9
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<<field) != 0
|
||||
}
|
||||
|
||||
// Add adds a Field to the mask.
|
||||
func (fm *FieldMask) Add(field Field) {
|
||||
fm.mask |= 1 << field
|
||||
}
|
||||
|
||||
// Remove removes a Field from the mask.
|
||||
func (fm *FieldMask) Remove(field Field) {
|
||||
fm.mask &^= 1 << field
|
||||
}
|
||||
|
||||
// Empty returns true if no bits are set.
|
||||
func (fm *FieldMask) Empty() bool {
|
||||
return fm.mask == 0
|
||||
}
|
||||
|
||||
// A Checker performs security checks at checkpoints.
|
||||
//
|
||||
// Each Checker method X is called at checkpoint X; if the method may return a
|
||||
@@ -44,48 +110,41 @@ const (
|
||||
// immediately (without calling subsequent Checkers) and return the error. The
|
||||
// info argument contains information relevant to the check. The mask argument
|
||||
// indicates what fields in info are valid; the mask should usually be a
|
||||
// superset of fields requested by the Checker's corresponding CheckerReq, but
|
||||
// superset of fields requested by the Checker's corresponding PointReq, but
|
||||
// may be missing requested fields in some cases (e.g. if the Checker is
|
||||
// registered concurrently with invocations of checkpoints).
|
||||
type Checker interface {
|
||||
Clone(ctx context.Context, mask CloneFieldSet, info CloneInfo) error
|
||||
Execve(ctx context.Context, mask ExecveFieldSet, info ExecveInfo) error
|
||||
ExitNotifyParent(ctx context.Context, mask ExitNotifyParentFieldSet, info ExitNotifyParentInfo) error
|
||||
Clone(ctx context.Context, fields FieldSet, info *pb.CloneInfo) error
|
||||
Execve(ctx context.Context, fields FieldSet, info *pb.ExecveInfo) error
|
||||
ExitNotifyParent(ctx context.Context, fields FieldSet, info *pb.ExitNotifyParentInfo) error
|
||||
}
|
||||
|
||||
// CheckerDefaults may be embedded by implementations of Checker to obtain
|
||||
// no-op implementations of Checker methods that may be explicitly overridden.
|
||||
type CheckerDefaults struct{}
|
||||
|
||||
var _ Checker = (*CheckerDefaults)(nil)
|
||||
|
||||
// Clone implements Checker.Clone.
|
||||
func (CheckerDefaults) Clone(ctx context.Context, mask CloneFieldSet, info CloneInfo) error {
|
||||
func (CheckerDefaults) Clone(context.Context, FieldSet, *pb.CloneInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execve implements Checker.Execve.
|
||||
func (CheckerDefaults) Execve(ctx context.Context, mask ExecveFieldSet, info ExecveInfo) error {
|
||||
func (CheckerDefaults) Execve(context.Context, FieldSet, *pb.ExecveInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExitNotifyParent implements Checker.ExitNotifyParent.
|
||||
func (CheckerDefaults) ExitNotifyParent(ctx context.Context, mask ExitNotifyParentFieldSet, info ExitNotifyParentInfo) error {
|
||||
func (CheckerDefaults) ExitNotifyParent(context.Context, FieldSet, *pb.ExitNotifyParentInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckerReq indicates what checkpoints a corresponding Checker runs at, and
|
||||
// what information it requires at those checkpoints.
|
||||
type CheckerReq struct {
|
||||
// Points are the set of checkpoints for which the corresponding Checker
|
||||
// must be called. Note that methods not specified in Points may still be
|
||||
// called; implementations of Checker may embed CheckerDefaults to obtain
|
||||
// no-op implementations of Checker methods.
|
||||
Points []Point
|
||||
|
||||
// All of the following fields indicate what fields in the corresponding
|
||||
// XInfo struct will be requested at the corresponding checkpoint.
|
||||
Clone CloneFields
|
||||
Execve ExecveFields
|
||||
ExitNotifyParent ExitNotifyParentFields
|
||||
// PointReq indicates what Point a corresponding Checker runs at, and what
|
||||
// information it requires at those Points.
|
||||
type PointReq struct {
|
||||
Pt Point
|
||||
Fields FieldSet
|
||||
}
|
||||
|
||||
// Global is the method receiver of all seccheck functions.
|
||||
@@ -95,7 +154,7 @@ var Global State
|
||||
type State struct {
|
||||
// registrationMu serializes all changes to the set of registered Checkers
|
||||
// for all checkpoints.
|
||||
registrationMu sync.Mutex
|
||||
registrationMu sync.RWMutex
|
||||
|
||||
// enabledPoints is a bitmask of checkpoints for which at least one Checker
|
||||
// is registered.
|
||||
@@ -113,30 +172,25 @@ type State struct {
|
||||
// Mutation of checkers is serialized by registrationMu.
|
||||
checkers []Checker
|
||||
|
||||
// All of the following xReq variables indicate what fields in the
|
||||
// corresponding XInfo struct have been requested by any registered
|
||||
// checker, are accessed using atomic memory operations, and are mutated
|
||||
// with registrationMu locked.
|
||||
cloneReq CloneFieldSet
|
||||
execveReq ExecveFieldSet
|
||||
exitNotifyParentReq ExitNotifyParentFieldSet
|
||||
pointFields map[Point]FieldSet
|
||||
}
|
||||
|
||||
// AppendChecker registers the given Checker to execute at checkpoints. The
|
||||
// Checker will execute after all previously-registered Checkers, and only if
|
||||
// those Checkers return a nil error.
|
||||
func (s *State) AppendChecker(c Checker, req *CheckerReq) {
|
||||
func (s *State) AppendChecker(c Checker, reqs []PointReq) {
|
||||
s.registrationMu.Lock()
|
||||
defer s.registrationMu.Unlock()
|
||||
|
||||
s.cloneReq.AddFieldsLoadable(req.Clone)
|
||||
s.execveReq.AddFieldsLoadable(req.Execve)
|
||||
s.exitNotifyParentReq.AddFieldsLoadable(req.ExitNotifyParent)
|
||||
|
||||
s.appendCheckerLocked(c)
|
||||
for _, p := range req.Points {
|
||||
word, bit := p/32, p%32
|
||||
if s.pointFields == nil {
|
||||
s.pointFields = make(map[Point]FieldSet)
|
||||
}
|
||||
for _, req := range reqs {
|
||||
word, bit := req.Pt/32, req.Pt%32
|
||||
atomic.StoreUint32(&s.enabledPoints[word], s.enabledPoints[word]|(uint32(1)<<bit))
|
||||
|
||||
s.pointFields[req.Pt] = req.Fields
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,3 +210,10 @@ func (s *State) appendCheckerLocked(c Checker) {
|
||||
s.checkers = append(s.checkers, c)
|
||||
s.registrationSeq.EndWrite()
|
||||
}
|
||||
|
||||
// GetFieldSet returns the FieldSet that has been configured for a given Point.
|
||||
func (s *State) GetFieldSet(p Point) FieldSet {
|
||||
s.registrationMu.RLock()
|
||||
defer s.registrationMu.RUnlock()
|
||||
return s.pointFields[p]
|
||||
}
|
||||
|
||||
@@ -19,20 +19,21 @@ import (
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
|
||||
)
|
||||
|
||||
type testChecker struct {
|
||||
CheckerDefaults
|
||||
|
||||
onClone func(ctx context.Context, mask CloneFieldSet, info CloneInfo) error
|
||||
onClone func(ctx context.Context, fields FieldSet, info *pb.CloneInfo) error
|
||||
}
|
||||
|
||||
// Clone implements Checker.Clone.
|
||||
func (c *testChecker) Clone(ctx context.Context, mask CloneFieldSet, info CloneInfo) error {
|
||||
func (c *testChecker) Clone(ctx context.Context, fields FieldSet, info *pb.CloneInfo) error {
|
||||
if c.onClone == nil {
|
||||
return nil
|
||||
}
|
||||
return c.onClone(ctx, mask, info)
|
||||
return c.onClone(ctx, fields, info)
|
||||
}
|
||||
|
||||
func TestNoChecker(t *testing.T) {
|
||||
@@ -44,7 +45,7 @@ func TestNoChecker(t *testing.T) {
|
||||
|
||||
func TestCheckerNotRegisteredForPoint(t *testing.T) {
|
||||
var s State
|
||||
s.AppendChecker(&testChecker{}, &CheckerReq{})
|
||||
s.AppendChecker(&testChecker{}, nil)
|
||||
if s.Enabled(PointClone) {
|
||||
t.Errorf("Enabled(PointClone): got true, wanted false")
|
||||
}
|
||||
@@ -53,23 +54,28 @@ func TestCheckerNotRegisteredForPoint(t *testing.T) {
|
||||
func TestCheckerRegistered(t *testing.T) {
|
||||
var s State
|
||||
checkerCalled := false
|
||||
s.AppendChecker(&testChecker{onClone: func(ctx context.Context, mask CloneFieldSet, info CloneInfo) error {
|
||||
checkerCalled = true
|
||||
return nil
|
||||
}}, &CheckerReq{
|
||||
Points: []Point{PointClone},
|
||||
Clone: CloneFields{
|
||||
Credentials: true,
|
||||
checker := &testChecker{
|
||||
onClone: func(context.Context, FieldSet, *pb.CloneInfo) error {
|
||||
checkerCalled = true
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
req := []PointReq{
|
||||
{
|
||||
Pt: PointClone,
|
||||
Fields: FieldSet{Context: MakeFieldMask(FieldCtxtCredentials)},
|
||||
},
|
||||
}
|
||||
s.AppendChecker(checker, req)
|
||||
|
||||
if !s.Enabled(PointClone) {
|
||||
t.Errorf("Enabled(PointClone): got false, wanted true")
|
||||
}
|
||||
if !s.CloneReq().Contains(CloneFieldCredentials) {
|
||||
t.Errorf("CloneReq().Contains(CloneFieldCredentials): got false, wanted true")
|
||||
fields := s.GetFieldSet(PointClone)
|
||||
if !fields.Context.Contains(FieldCtxtCredentials) {
|
||||
t.Errorf("fields.Context.Contains(PointContextCredentials): got false, wanted true")
|
||||
}
|
||||
if err := s.Clone(context.Background(), CloneFieldSet{}, &CloneInfo{}); err != nil {
|
||||
if err := s.Clone(context.Background(), fields, &pb.CloneInfo{}); err != nil {
|
||||
t.Errorf("Clone(): got %v, wanted nil", err)
|
||||
}
|
||||
if !checkerCalled {
|
||||
@@ -80,40 +86,33 @@ func TestCheckerRegistered(t *testing.T) {
|
||||
func TestMultipleCheckersRegistered(t *testing.T) {
|
||||
var s State
|
||||
checkersCalled := [2]bool{}
|
||||
s.AppendChecker(&testChecker{onClone: func(ctx context.Context, mask CloneFieldSet, info CloneInfo) error {
|
||||
checkersCalled[0] = true
|
||||
return nil
|
||||
}}, &CheckerReq{
|
||||
Points: []Point{PointClone},
|
||||
Clone: CloneFields{
|
||||
Args: true,
|
||||
checker := &testChecker{
|
||||
onClone: func(context.Context, FieldSet, *pb.CloneInfo) error {
|
||||
checkersCalled[0] = true
|
||||
return nil
|
||||
},
|
||||
})
|
||||
s.AppendChecker(&testChecker{onClone: func(ctx context.Context, mask CloneFieldSet, info CloneInfo) error {
|
||||
}
|
||||
reqs := []PointReq{
|
||||
{Pt: PointClone},
|
||||
}
|
||||
s.AppendChecker(checker, reqs)
|
||||
|
||||
checker = &testChecker{onClone: func(context.Context, FieldSet, *pb.CloneInfo) error {
|
||||
checkersCalled[1] = true
|
||||
return nil
|
||||
}}, &CheckerReq{
|
||||
Points: []Point{PointClone},
|
||||
Clone: CloneFields{
|
||||
Created: TaskFields{
|
||||
ThreadID: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}}
|
||||
reqs = []PointReq{
|
||||
{Pt: PointClone},
|
||||
}
|
||||
s.AppendChecker(checker, reqs)
|
||||
|
||||
if !s.Enabled(PointClone) {
|
||||
t.Errorf("Enabled(PointClone): got false, wanted true")
|
||||
}
|
||||
// CloneReq() should return the union of requested fields from all calls to
|
||||
// AppendChecker.
|
||||
req := s.CloneReq()
|
||||
if !req.Contains(CloneFieldArgs) {
|
||||
t.Errorf("req.Contains(CloneFieldArgs): got false, wanted true")
|
||||
}
|
||||
if !req.Created.Contains(TaskFieldThreadID) {
|
||||
t.Errorf("req.Created.Contains(TaskFieldThreadID): got false, wanted true")
|
||||
}
|
||||
if err := s.Clone(context.Background(), CloneFieldSet{}, &CloneInfo{}); err != nil {
|
||||
fields := s.GetFieldSet(PointClone)
|
||||
if err := s.Clone(context.Background(), fields, &pb.CloneInfo{}); err != nil {
|
||||
t.Errorf("Clone(): got %v, wanted nil", err)
|
||||
}
|
||||
for i := range checkersCalled {
|
||||
@@ -129,23 +128,30 @@ func TestCheckpointReturnsFirstCheckerError(t *testing.T) {
|
||||
|
||||
var s State
|
||||
checkersCalled := [2]bool{}
|
||||
s.AppendChecker(&testChecker{onClone: func(ctx context.Context, mask CloneFieldSet, info CloneInfo) error {
|
||||
checkersCalled[0] = true
|
||||
return errFirstChecker
|
||||
}}, &CheckerReq{
|
||||
Points: []Point{PointClone},
|
||||
})
|
||||
s.AppendChecker(&testChecker{onClone: func(ctx context.Context, mask CloneFieldSet, info CloneInfo) error {
|
||||
checkersCalled[1] = true
|
||||
return errSecondChecker
|
||||
}}, &CheckerReq{
|
||||
Points: []Point{PointClone},
|
||||
})
|
||||
checker := &testChecker{
|
||||
onClone: func(context.Context, FieldSet, *pb.CloneInfo) error {
|
||||
checkersCalled[0] = true
|
||||
return errFirstChecker
|
||||
},
|
||||
}
|
||||
reqs := []PointReq{
|
||||
{Pt: PointClone},
|
||||
}
|
||||
|
||||
s.AppendChecker(checker, reqs)
|
||||
|
||||
checker = &testChecker{
|
||||
onClone: func(context.Context, FieldSet, *pb.CloneInfo) error {
|
||||
checkersCalled[1] = true
|
||||
return errSecondChecker
|
||||
},
|
||||
}
|
||||
s.AppendChecker(checker, reqs)
|
||||
|
||||
if !s.Enabled(PointClone) {
|
||||
t.Errorf("Enabled(PointClone): got false, wanted true")
|
||||
}
|
||||
if err := s.Clone(context.Background(), CloneFieldSet{}, &CloneInfo{}); err != errFirstChecker {
|
||||
if err := s.Clone(context.Background(), FieldSet{}, &pb.CloneInfo{}); err != errFirstChecker {
|
||||
t.Errorf("Clone(): got %v, wanted %v", err, errFirstChecker)
|
||||
}
|
||||
if !checkersCalled[0] {
|
||||
@@ -155,3 +161,92 @@ func TestCheckpointReturnsFirstCheckerError(t *testing.T) {
|
||||
t.Errorf("Clone() called second Checker")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldMaskEmpty(t *testing.T) {
|
||||
fd := FieldMask{}
|
||||
if !fd.Empty() {
|
||||
t.Errorf("new FieldMask must be empty: %+v", fd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldMaskMake(t *testing.T) {
|
||||
zero := Field(0)
|
||||
one := Field(1)
|
||||
two := Field(2)
|
||||
fd := MakeFieldMask(zero, two)
|
||||
if fd.Empty() {
|
||||
t.Errorf("FieldMask must not be empty: %+v", fd)
|
||||
}
|
||||
if want := zero; !fd.Contains(want) {
|
||||
t.Errorf("FieldMask must contain %v: %+v", want, fd)
|
||||
}
|
||||
if want := two; !fd.Contains(want) {
|
||||
t.Errorf("FieldMask must contain %v: %+v", want, fd)
|
||||
}
|
||||
if want := one; fd.Contains(want) {
|
||||
t.Errorf("FieldMask must not contain %v: %+v", want, fd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldMask(t *testing.T) {
|
||||
zero := Field(0)
|
||||
one := Field(1)
|
||||
two := Field(2)
|
||||
fd := FieldMask{}
|
||||
|
||||
fd.Add(zero)
|
||||
if fd.Empty() {
|
||||
t.Errorf("FieldMask must not be empty: %+v", fd)
|
||||
}
|
||||
if want := zero; !fd.Contains(want) {
|
||||
t.Errorf("FieldMask must contain %v: %+v", want, fd)
|
||||
}
|
||||
if want := one; fd.Contains(want) {
|
||||
t.Errorf("FieldMask must not contain %v: %+v", want, fd)
|
||||
}
|
||||
if want := two; fd.Contains(want) {
|
||||
t.Errorf("FieldMask must not contain %v: %+v", want, fd)
|
||||
}
|
||||
|
||||
fd.Add(two)
|
||||
if fd.Empty() {
|
||||
t.Errorf("FieldMask must not be empty: %+v", fd)
|
||||
}
|
||||
if want := zero; !fd.Contains(want) {
|
||||
t.Errorf("FieldMask must contain %v: %+v", want, fd)
|
||||
}
|
||||
if want := one; fd.Contains(want) {
|
||||
t.Errorf("FieldMask must not contain %v: %+v", want, fd)
|
||||
}
|
||||
if want := two; !fd.Contains(want) {
|
||||
t.Errorf("FieldMask must contain %v: %+v", want, fd)
|
||||
}
|
||||
|
||||
fd.Remove(zero)
|
||||
if fd.Empty() {
|
||||
t.Errorf("FieldMask must not be empty: %+v", fd)
|
||||
}
|
||||
if want := zero; fd.Contains(want) {
|
||||
t.Errorf("FieldMask must not contain %v: %+v", want, fd)
|
||||
}
|
||||
if want := one; fd.Contains(want) {
|
||||
t.Errorf("FieldMask must not contain %v: %+v", want, fd)
|
||||
}
|
||||
if want := two; !fd.Contains(want) {
|
||||
t.Errorf("FieldMask must contain %v: %+v", want, fd)
|
||||
}
|
||||
|
||||
fd.Remove(two)
|
||||
if !fd.Empty() {
|
||||
t.Errorf("FieldMask must be empty: %+v", fd)
|
||||
}
|
||||
if want := zero; fd.Contains(want) {
|
||||
t.Errorf("FieldMask must not contain %v: %+v", want, fd)
|
||||
}
|
||||
if want := one; fd.Contains(want) {
|
||||
t.Errorf("FieldMask must not contain %v: %+v", want, fd)
|
||||
}
|
||||
if want := two; fd.Contains(want) {
|
||||
t.Errorf("FieldMask must not contain %v: %+v", want, fd)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright 2021 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 seccheck
|
||||
|
||||
import (
|
||||
ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
|
||||
)
|
||||
|
||||
// TaskInfo contains information unambiguously identifying a single thread
|
||||
// and/or its containing process.
|
||||
//
|
||||
// +fieldenum Task
|
||||
type TaskInfo struct {
|
||||
// ThreadID is the thread's ID in the root PID namespace.
|
||||
ThreadID int32
|
||||
|
||||
// ThreadStartTime is the thread's CLOCK_REALTIME start time.
|
||||
ThreadStartTime ktime.Time
|
||||
|
||||
// ThreadGroupID is the thread's group leader's ID in the root PID
|
||||
// namespace.
|
||||
ThreadGroupID int32
|
||||
|
||||
// ThreadGroupStartTime is the thread's group leader's CLOCK_REALTIME start
|
||||
// time.
|
||||
ThreadGroupStartTime ktime.Time
|
||||
}
|
||||
Reference in New Issue
Block a user