Skip to content

Commit f45c79e

Browse files
EtiennePerotgvisor-bot
authored andcommitted
Add /proc/gvisor/kernel_is_gvisor marker file used to detect gVisor.
This change adds a configuration option to gVisor that, when enabled, creates a file at `/proc/gvisor/kernel_is_gvisor` that can be used by applications to detect whether they are running inside a gVisor sandbox. Useful for testing and introspection. PiperOrigin-RevId: 749217117
1 parent 4b740a7 commit f45c79e

File tree

11 files changed

+138
-4
lines changed

11 files changed

+138
-4
lines changed

pkg/gvisordetect/BUILD

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
load("//tools:defs.bzl", "go_library")
2+
3+
package(
4+
default_applicable_licenses = ["//:license"],
5+
licenses = ["notice"],
6+
)
7+
8+
go_library(
9+
name = "gvisordetect",
10+
srcs = ["gvisordetect.go"],
11+
visibility = ["//visibility:public"],
12+
)

pkg/gvisordetect/gvisordetect.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2025 The gVisor Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package gvisordetect implements a library that callers may use to detect
16+
// whether they are running on a gVisor kernel, assuming it is configured
17+
// to expose the gVisor marker file.
18+
package gvisordetect
19+
20+
import (
21+
"errors"
22+
"fmt"
23+
"os"
24+
)
25+
26+
// RunningInGVisor checks if the caller is running against a gVisor kernel.
27+
// This is only accurate if the gVisor kernel is configured to expose
28+
// its marker file.
29+
func RunningInGVisor() (bool, error) {
30+
const markerFilePath = "/proc/gvisor/kernel_is_gvisor"
31+
32+
_, err := os.Stat(markerFilePath)
33+
if err == nil {
34+
return true, nil
35+
}
36+
if errors.Is(err, os.ErrNotExist) {
37+
return false, nil
38+
}
39+
return false, fmt.Errorf("cannot detect whether running in gVisor: %w", err)
40+
}

pkg/sentry/fsimpl/proc/filesystem.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,12 @@ func (fs *filesystem) newStaticDir(ctx context.Context, creds *auth.Credentials,
159159
// +stateify savable
160160
type InternalData struct {
161161
ExtraInternalData
162+
163+
// GVisorMarkerFile indicates whether a file named gvisor/kernel_is_gvisor
164+
// should exist in the procfs.
165+
GVisorMarkerFile bool
166+
167+
// Cgroups-internal data.
162168
Cgroups map[string]string
163169
}
164170

pkg/sentry/fsimpl/proc/proc_impl.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,10 @@ import (
2929
// +stateify savable
3030
type ExtraInternalData struct{}
3131

32-
func (fs *filesystem) newTasksInodeExtra(context.Context, *auth.Credentials, *InternalData, *kernel.Kernel, map[string]kernfs.Inode) {
32+
func (fs *filesystem) newTasksInodeExtra(ctx context.Context, root *auth.Credentials, internalData *InternalData, _ *kernel.Kernel, nodes map[string]kernfs.Inode) {
33+
if internalData.GVisorMarkerFile {
34+
nodes["gvisor"] = fs.newStaticDir(ctx, root, map[string]kernfs.Inode{
35+
"kernel_is_gvisor": newStaticFile("gvisor\n"),
36+
})
37+
}
3338
}

runsc/boot/restore_impl.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
specs "github.com/opencontainers/runtime-spec/specs-go"
2222
"gvisor.dev/gvisor/pkg/sentry/control"
2323
"gvisor.dev/gvisor/pkg/sentry/fsimpl/proc"
24+
"gvisor.dev/gvisor/runsc/config"
2425
)
2526

2627
func preSaveImpl(*Loader, *control.SaveOpts) error {
@@ -37,8 +38,10 @@ func postResumeImpl(*Loader) error {
3738
return nil
3839
}
3940

40-
func newProcInternalData(*specs.Spec) *proc.InternalData {
41-
return &proc.InternalData{}
41+
func newProcInternalData(conf *config.Config, _ *specs.Spec) *proc.InternalData {
42+
return &proc.InternalData{
43+
GVisorMarkerFile: conf.GVisorMarkerFile,
44+
}
4245
}
4346

4447
func (l *Loader) kernelInitExtra() {}

runsc/boot/vfs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,7 @@ func getMountNameAndOptions(spec *specs.Spec, conf *config.Config, m *mountInfo,
872872
fsName = sys.Name
873873

874874
case proc.Name:
875-
internalData = newProcInternalData(spec)
875+
internalData = newProcInternalData(conf, spec)
876876

877877
case sys.Name:
878878
sysData := &sys.InternalData{EnableTPUProxyPaths: specutils.TPUProxyIsEnabled(spec, conf)}

runsc/config/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@ type Config struct {
388388
// RestoreSpecValidation indicates the level of spec validation to be
389389
// performed during restore.
390390
RestoreSpecValidation RestoreSpecValidationPolicy `flag:"restore-spec-validation"`
391+
392+
// GVisorMarkerFile enables the /proc/gvisor/kernel_is_gvisor marker file.
393+
GVisorMarkerFile bool `flag:"gvisor-marker-file"`
391394
}
392395

393396
func (c *Config) validate() error {

runsc/config/flags.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ func RegisterFlags(flagSet *flag.FlagSet) {
119119
flagSet.Bool("fsgofer-host-uds", false, "DEPRECATED: use host-uds=all")
120120
flagSet.Var(hostUDSPtr(HostUDSNone), flagHostUDS, "controls permission to access host Unix-domain sockets. Values: none|open|create|all, default: none")
121121
flagSet.Var(hostFifoPtr(HostFifoNone), "host-fifo", "controls permission to access host FIFOs (or named pipes). Values: none|open, default: none")
122+
flagSet.Bool("gvisor-marker-file", false, "enable the presence of the /proc/gvisor/kernel_is_gvisor file that can be used by applications to detect that gVisor is in use")
122123

123124
flagSet.Bool("vfs2", true, "DEPRECATED: this flag has no effect.")
124125
flagSet.Bool("fuse", true, "DEPRECATED: this flag has no effect.")

runsc/container/container_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4093,6 +4093,26 @@ func TestCheckpointResume(t *testing.T) {
40934093
}
40944094
}
40954095

4096+
func TestMarkerFile(t *testing.T) {
4097+
app, err := testutil.FindFile("test/cmd/test_app/test_app")
4098+
if err != nil {
4099+
t.Fatal("error finding test_app:", err)
4100+
}
4101+
conf := testutil.TestConfig(t)
4102+
4103+
conf.GVisorMarkerFile = false
4104+
spec := testutil.NewSpecWithArgs(app, "gvisor-detect", "--exit-code-on-gvisor=1", "--exit-code-on-not-gvisor=0")
4105+
if err := run(spec, conf); err != nil {
4106+
t.Fatalf("unexpectedly detected gVisor when we expected to not be able to do so: %v", err)
4107+
}
4108+
4109+
conf.GVisorMarkerFile = true
4110+
spec = testutil.NewSpecWithArgs(app, "gvisor-detect", "--exit-code-on-gvisor=0", "--exit-code-on-not-gvisor=1")
4111+
if err := run(spec, conf); err != nil {
4112+
t.Fatalf("failed to detect gVisor when we expected to be able to do so: %v", err)
4113+
}
4114+
}
4115+
40964116
func TestIPv6DisableAllSysctl(t *testing.T) {
40974117
tests := []struct {
40984118
name string

test/cmd/test_app/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ go_binary(
1919
"//test/syscalls/linux:__pkg__",
2020
],
2121
deps = [
22+
"//pkg/gvisordetect",
2223
"//pkg/rand",
2324
"//pkg/test/testutil",
2425
"//pkg/unet",

test/cmd/test_app/main.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434

3535
"github.com/google/subcommands"
3636
"github.com/kr/pty"
37+
"gvisor.dev/gvisor/pkg/gvisordetect"
3738
gvisorrand "gvisor.dev/gvisor/pkg/rand"
3839
"gvisor.dev/gvisor/pkg/test/testutil"
3940
"gvisor.dev/gvisor/runsc/flag"
@@ -47,6 +48,7 @@ func main() {
4748
subcommands.Register(new(fdSender), "")
4849
subcommands.Register(new(forkBomb), "")
4950
subcommands.Register(new(fsTreeCreator), "")
51+
subcommands.Register(new(gvisorDetect), "")
5052
subcommands.Register(new(ptyRunner), "")
5153
subcommands.Register(new(reaper), "")
5254
subcommands.Register(new(syscall), "")
@@ -275,6 +277,47 @@ func (c *taskTree) Execute(ctx context.Context, f *flag.FlagSet, args ...any) su
275277
return subcommands.ExitSuccess
276278
}
277279

280+
type gvisorDetect struct {
281+
exitCodeOnGVisor int
282+
exitCodeOnNotGVisor int
283+
}
284+
285+
// Name implements subcommands.Command.Name.
286+
func (*gvisorDetect) Name() string {
287+
return "gvisor-detect"
288+
}
289+
290+
// Synopsis implements subcommands.Command.Synopsys.
291+
func (*gvisorDetect) Synopsis() string {
292+
return "checks if the process is running inside gVisor by checking for the marker file"
293+
}
294+
295+
// Usage implements subcommands.Command.Usage.
296+
func (*gvisorDetect) Usage() string {
297+
return "gvisor-detect [flags]"
298+
}
299+
300+
// SetFlags implements subcommands.Command.SetFlags.
301+
func (c *gvisorDetect) SetFlags(f *flag.FlagSet) {
302+
f.IntVar(&c.exitCodeOnGVisor, "exit-code-on-gvisor", 0, "exit code to return if running in gVisor")
303+
f.IntVar(&c.exitCodeOnNotGVisor, "exit-code-on-not-gvisor", 42, "exit code to return if not running in gVisor")
304+
}
305+
306+
// Execute implements subcommands.Command.Execute.
307+
func (c *gvisorDetect) Execute(ctx context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus {
308+
isGVisor, err := gvisordetect.RunningInGVisor()
309+
if err != nil {
310+
fmt.Fprintf(os.Stderr, "Error checking whether we are running in gVisor: %v\n", err)
311+
return subcommands.ExitFailure
312+
}
313+
if isGVisor {
314+
fmt.Println("running with gVisor kernel")
315+
return subcommands.ExitStatus(c.exitCodeOnGVisor)
316+
}
317+
fmt.Println("running with non-gVisor kernel")
318+
return subcommands.ExitStatus(c.exitCodeOnNotGVisor)
319+
}
320+
278321
type forkBomb struct {
279322
delay time.Duration
280323
}

0 commit comments

Comments
 (0)