From a536d0e7b457fc09991701fb8f60bfc45e1fe6b7 Mon Sep 17 00:00:00 2001 From: Xin Zhong Date: Mon, 13 Jul 2026 13:14:52 -0700 Subject: [PATCH] Add executable ctime to ExecveBinaryInfo. This change includes the creation time (ctime) of the executable binary in the seccheck ExecveBinaryInfo and MmapInfo. The Timespec proto message is moved to common.proto for reuse. PiperOrigin-RevId: 947205599 --- pkg/sentry/kernel/task_exec.go | 8 +++++++- pkg/sentry/seccheck/points/common.proto | 5 +++++ pkg/sentry/seccheck/points/sentry.proto | 6 ++++++ pkg/sentry/seccheck/points/syscall.proto | 9 ++------- pkg/sentry/syscalls/linux/sys_mmap.go | 8 +++++++- test/trace/trace_test.go | 6 ++++++ 6 files changed, 33 insertions(+), 9 deletions(-) diff --git a/pkg/sentry/kernel/task_exec.go b/pkg/sentry/kernel/task_exec.go index 20c74ef637..56b2673fd1 100644 --- a/pkg/sentry/kernel/task_exec.go +++ b/pkg/sentry/kernel/task_exec.go @@ -473,7 +473,7 @@ func getExecveSeccheckInfo(t *Task, argv, env []string, executable *vfs.FileDesc if fields.Local.Contains(seccheck.FieldSentryExecveBinaryInfo) { info.BinaryOverlayfsUpper = overlay.IsCopiedUp(executable.Dentry()) statOpts := vfs.StatOptions{ - Mask: linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID | linux.STATX_INO, + Mask: linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID | linux.STATX_INO | linux.STATX_CTIME, } if stat, err := executable.Stat(t, statOpts); err == nil { if stat.Mask&(linux.STATX_TYPE|linux.STATX_MODE) == (linux.STATX_TYPE | linux.STATX_MODE) { @@ -488,6 +488,12 @@ func getExecveSeccheckInfo(t *Task, argv, env []string, executable *vfs.FileDesc if stat.Mask&linux.STATX_INO != 0 { info.BinaryIno = stat.Ino } + if stat.Mask&linux.STATX_CTIME != 0 { + info.BinaryCtime = &pb.Timespec{ + Sec: stat.Ctime.Sec, + Nsec: int64(stat.Ctime.Nsec), + } + } } } diff --git a/pkg/sentry/seccheck/points/common.proto b/pkg/sentry/seccheck/points/common.proto index 0c90823ff1..a265cf95ad 100644 --- a/pkg/sentry/seccheck/points/common.proto +++ b/pkg/sentry/seccheck/points/common.proto @@ -64,6 +64,11 @@ message Handshake { uint32 version = 1; } +message Timespec { + int64 sec = 1; + int64 nsec = 2; +} + message Credentials { uint32 real_uid = 1; uint32 effective_uid = 2; diff --git a/pkg/sentry/seccheck/points/sentry.proto b/pkg/sentry/seccheck/points/sentry.proto index 0d264e723f..b5f143e236 100644 --- a/pkg/sentry/seccheck/points/sentry.proto +++ b/pkg/sentry/seccheck/points/sentry.proto @@ -66,6 +66,9 @@ message ExecveInfo { // BinaryIno is the virtualized inode number of the executable binary file. uint64 binary_ino = 10; + + // BinaryCtime is the ctime of the executable binary file. + gvisor.common.Timespec binary_ctime = 11; } message ExitNotifyParentInfo { @@ -105,4 +108,7 @@ message MmapInfo { // IsInitialMmap indicates whether this mapping corresponds to the task's // initial executable file. bool is_initial_mmap = 7; + + // MappedCtime is the ctime of the backing file. + gvisor.common.Timespec mapped_ctime = 8; } diff --git a/pkg/sentry/seccheck/points/syscall.proto b/pkg/sentry/seccheck/points/syscall.proto index 011d62a583..6da98c9fc4 100644 --- a/pkg/sentry/seccheck/points/syscall.proto +++ b/pkg/sentry/seccheck/points/syscall.proto @@ -239,14 +239,9 @@ message TimerfdCreate { int32 flags = 5; } -message Timespec { - int64 sec = 1; - int64 nsec = 2; -} - message ItimerSpec { - Timespec interval = 1; - Timespec value = 2; + gvisor.common.Timespec interval = 1; + gvisor.common.Timespec value = 2; } message TimerfdSetTime { diff --git a/pkg/sentry/syscalls/linux/sys_mmap.go b/pkg/sentry/syscalls/linux/sys_mmap.go index 58c8f00bc2..7cf27277a6 100644 --- a/pkg/sentry/syscalls/linux/sys_mmap.go +++ b/pkg/sentry/syscalls/linux/sys_mmap.go @@ -369,7 +369,7 @@ func traceMmap(t *kernel.Task, file *vfs.FileDescription) error { if file != nil { info.MappedPath = file.MappedName(t) statOpts := vfs.StatOptions{ - Mask: linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID | linux.STATX_INO, + Mask: linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID | linux.STATX_INO | linux.STATX_CTIME, } if stat, err := file.Stat(t, statOpts); err == nil { if stat.Mask&(linux.STATX_TYPE|linux.STATX_MODE) == (linux.STATX_TYPE | linux.STATX_MODE) { @@ -384,6 +384,12 @@ func traceMmap(t *kernel.Task, file *vfs.FileDescription) error { if stat.Mask&linux.STATX_INO != 0 { info.MappedIno = stat.Ino } + if stat.Mask&linux.STATX_CTIME != 0 { + info.MappedCtime = &ppb.Timespec{ + Sec: stat.Ctime.Sec, + Nsec: int64(stat.Ctime.Nsec), + } + } } } if exe := t.MemoryManager().Executable(); exe != nil { diff --git a/test/trace/trace_test.go b/test/trace/trace_test.go index 85a695ea5f..687966a708 100644 --- a/test/trace/trace_test.go +++ b/test/trace/trace_test.go @@ -273,6 +273,9 @@ func checkSentryMmap(msg test.Message) error { if p.IsInitialMmap || p.MappedPath != "" { fmt.Printf("Mmap event: path=%q, ino=%d, mode=%o, uid=%d, gid=%d, initial=%v\n", p.MappedPath, p.MappedIno, p.MappedMode, p.MappedUid, p.MappedGid, p.IsInitialMmap) } + if p.MappedPath != "" && (p.MappedCtime == nil || p.MappedCtime.Sec == 0) { + return fmt.Errorf("MappedCtime should not be empty for mapped file: %q", p.MappedPath) + } if err := checkContextData(p.ContextData); err != nil { return err } @@ -431,6 +434,9 @@ func checkSentryExec(msg test.Message) error { if p.BinaryIno == 0 { return fmt.Errorf("BinaryIno should not be 0") } + if p.BinaryCtime == nil || p.BinaryCtime.Sec == 0 { + return fmt.Errorf("BinaryCtime should not be empty") + } // Get SHA256 from the binary and compare it with the one from the event. out, err := exec.Command("sha256sum", p.BinaryPath).CombinedOutput()