mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add product_name to sysfs
Some applications use product_name to find out if they are running inside cloud providers and behave differently, like changing network settings or expecting services to be available to the VM. More details here: https://docs.google.com/document/d/1xQXE27x9wTvwPsgiX9Hn0o8mcq5z3SKi-1jwscQsCAk This is also needed for: go/auth-library-noping PiperOrigin-RevId: 435454155
This commit is contained in:
committed by
gVisor bot
parent
334090575d
commit
49896299a1
@@ -34,6 +34,7 @@ import (
|
||||
const (
|
||||
// Name is the default filesystem name.
|
||||
Name = "sysfs"
|
||||
defaultSysMode = linux.FileMode(0444)
|
||||
defaultSysDirMode = linux.FileMode(0755)
|
||||
defaultMaxCachedDentries = uint64(1000)
|
||||
)
|
||||
@@ -43,6 +44,15 @@ const (
|
||||
// +stateify savable
|
||||
type FilesystemType struct{}
|
||||
|
||||
// InternalData contains internal data passed in via
|
||||
// vfs.GetFilesystemOptions.InternalData.
|
||||
//
|
||||
// +stateify savable
|
||||
type InternalData struct {
|
||||
// ProductName is the value to be set to devices/virtual/dmi/id/product_name.
|
||||
ProductName string
|
||||
}
|
||||
|
||||
// filesystem implements vfs.FilesystemImpl.
|
||||
//
|
||||
// +stateify savable
|
||||
@@ -96,18 +106,38 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
||||
fsDirChildren["cgroup"] = fs.newDir(ctx, creds, defaultSysDirMode, nil)
|
||||
}
|
||||
|
||||
root := fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
"block": fs.newDir(ctx, creds, defaultSysDirMode, nil),
|
||||
"bus": fs.newDir(ctx, creds, defaultSysDirMode, nil),
|
||||
"class": fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
"power_supply": fs.newDir(ctx, creds, defaultSysDirMode, nil),
|
||||
classSub := map[string]kernfs.Inode{
|
||||
"power_supply": fs.newDir(ctx, creds, defaultSysDirMode, nil),
|
||||
}
|
||||
devicesSub := map[string]kernfs.Inode{
|
||||
"system": fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
"cpu": cpuDir(ctx, fs, creds),
|
||||
}),
|
||||
"dev": fs.newDir(ctx, creds, defaultSysDirMode, nil),
|
||||
"devices": fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
"system": fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
"cpu": cpuDir(ctx, fs, creds),
|
||||
}
|
||||
productName := ""
|
||||
if opts.InternalData != nil {
|
||||
data := opts.InternalData.(*InternalData)
|
||||
productName = data.ProductName
|
||||
}
|
||||
if len(productName) > 0 {
|
||||
log.Debugf("Setting product_name: %q", productName)
|
||||
classSub["dmi"] = fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
"id": kernfs.NewStaticSymlink(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), "../../devices/virtual/dmi/id"),
|
||||
})
|
||||
devicesSub["virtual"] = fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
"dmi": fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
"id": fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
"product_name": fs.newStaticFile(ctx, creds, defaultSysMode, productName+"\n"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
}
|
||||
root := fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
"block": fs.newDir(ctx, creds, defaultSysDirMode, nil),
|
||||
"bus": fs.newDir(ctx, creds, defaultSysDirMode, nil),
|
||||
"class": fs.newDir(ctx, creds, defaultSysDirMode, classSub),
|
||||
"dev": fs.newDir(ctx, creds, defaultSysDirMode, nil),
|
||||
"devices": fs.newDir(ctx, creds, defaultSysDirMode, devicesSub),
|
||||
"firmware": fs.newDir(ctx, creds, defaultSysDirMode, nil),
|
||||
"fs": fs.newDir(ctx, creds, defaultSysDirMode, fsDirChildren),
|
||||
"kernel": kernelDir(ctx, fs, creds),
|
||||
@@ -239,3 +269,15 @@ type implStatFS struct{}
|
||||
func (*implStatFS) StatFS(context.Context, *vfs.Filesystem) (linux.Statfs, error) {
|
||||
return vfs.GenericStatFS(linux.SYSFS_MAGIC), nil
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type staticFile struct {
|
||||
kernfs.DynamicBytesFile
|
||||
vfs.StaticData
|
||||
}
|
||||
|
||||
func (fs *filesystem) newStaticFile(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, data string) kernfs.Inode {
|
||||
s := &staticFile{StaticData: vfs.StaticData{Data: data}}
|
||||
s.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), s, mode)
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -432,7 +432,7 @@ func (cm *containerManager) Restore(o *RestoreOpts, _ *struct{}) error {
|
||||
|
||||
// Set up the restore environment.
|
||||
ctx := k.SupervisorContext()
|
||||
mntr := newContainerMounter(&cm.l.root, cm.l.k, cm.l.mountHints, kernel.VFS2Enabled)
|
||||
mntr := newContainerMounter(&cm.l.root, cm.l.k, cm.l.mountHints, kernel.VFS2Enabled, cm.l.productName)
|
||||
if kernel.VFS2Enabled {
|
||||
ctx, err = mntr.configureRestore(ctx)
|
||||
if err != nil {
|
||||
|
||||
+11
-6
@@ -622,15 +622,20 @@ type containerMounter struct {
|
||||
k *kernel.Kernel
|
||||
|
||||
hints *podMountHints
|
||||
|
||||
// productName is the value to show in
|
||||
// /sys/devices/virtual/dmi/id/product_name.
|
||||
productName string
|
||||
}
|
||||
|
||||
func newContainerMounter(info *containerInfo, k *kernel.Kernel, hints *podMountHints, vfs2Enabled bool) *containerMounter {
|
||||
func newContainerMounter(info *containerInfo, k *kernel.Kernel, hints *podMountHints, vfs2Enabled bool, productName string) *containerMounter {
|
||||
return &containerMounter{
|
||||
root: info.spec.Root,
|
||||
mounts: compileMounts(info.spec, info.conf, vfs2Enabled),
|
||||
fds: fdDispenser{fds: info.goferFDs},
|
||||
k: k,
|
||||
hints: hints,
|
||||
root: info.spec.Root,
|
||||
mounts: compileMounts(info.spec, info.conf, vfs2Enabled),
|
||||
fds: fdDispenser{fds: info.goferFDs},
|
||||
k: k,
|
||||
hints: hints,
|
||||
productName: productName,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,6 +144,10 @@ type Loader struct {
|
||||
// mountHints provides extra information about mounts for containers that
|
||||
// apply to the entire pod.
|
||||
mountHints *podMountHints
|
||||
|
||||
// productName is the value to show in
|
||||
// /sys/devices/virtual/dmi/id/product_name.
|
||||
productName string
|
||||
}
|
||||
|
||||
// execID uniquely identifies a sentry process that is executed in a container.
|
||||
@@ -219,6 +223,9 @@ type Args struct {
|
||||
// TraceFD is the file descriptor to write a Go execution trace to.
|
||||
// Valid if >=0.
|
||||
TraceFD int
|
||||
// ProductName is the value to show in
|
||||
// /sys/devices/virtual/dmi/id/product_name.
|
||||
ProductName string
|
||||
}
|
||||
|
||||
// make sure stdioFDs are always the same on initial start and on restore
|
||||
@@ -424,6 +431,7 @@ func New(args Args) (*Loader, error) {
|
||||
mountHints: mountHints,
|
||||
root: info,
|
||||
stopProfiling: stopProfiling,
|
||||
productName: args.ProductName,
|
||||
}
|
||||
|
||||
// We don't care about child signals; some platforms can generate a
|
||||
@@ -769,7 +777,7 @@ func (l *Loader) createContainerProcess(root bool, cid string, info *containerIn
|
||||
}
|
||||
l.startGoferMonitor(cid, int32(info.goferFDs[0].FD()))
|
||||
|
||||
mntr := newContainerMounter(info, l.k, l.mountHints, kernel.VFS2Enabled)
|
||||
mntr := newContainerMounter(info, l.k, l.mountHints, kernel.VFS2Enabled, l.productName)
|
||||
if root {
|
||||
if err := mntr.processHints(info.conf, info.procArgs.Credentials); err != nil {
|
||||
return nil, nil, nil, err
|
||||
|
||||
@@ -447,7 +447,7 @@ func TestCreateMountNamespace(t *testing.T) {
|
||||
goferFDs: []*fd.FD{fd.New(sandEnd)},
|
||||
}
|
||||
|
||||
mntr := newContainerMounter(&info, nil, &podMountHints{}, false /* vfs2Enabled */)
|
||||
mntr := newContainerMounter(&info, nil, &podMountHints{}, false /* vfs2Enabled */, "")
|
||||
mns, err := mntr.createMountNamespace(ctx, conf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create mount namespace: %v", err)
|
||||
@@ -487,7 +487,7 @@ func TestCreateMountNamespaceVFS2(t *testing.T) {
|
||||
defer l.Destroy()
|
||||
defer loaderCleanup()
|
||||
|
||||
mntr := newContainerMounter(&l.root, l.k, l.mountHints, true /* vfs2Enabled */)
|
||||
mntr := newContainerMounter(&l.root, l.k, l.mountHints, true /* vfs2Enabled */, "")
|
||||
if err := mntr.processHints(l.root.conf, l.root.procArgs.Credentials); err != nil {
|
||||
t.Fatalf("failed process hints: %v", err)
|
||||
}
|
||||
@@ -716,7 +716,7 @@ func TestRestoreEnvironment(t *testing.T) {
|
||||
spec: tc.spec,
|
||||
goferFDs: ioFDs,
|
||||
}
|
||||
mntr := newContainerMounter(&info, nil, &podMountHints{}, conf.VFS2)
|
||||
mntr := newContainerMounter(&info, nil, &podMountHints{}, conf.VFS2, "")
|
||||
actualRenv, err := mntr.createRestoreEnvironment(conf)
|
||||
if !tc.errorExpected && err != nil {
|
||||
t.Fatalf("could not create restore environment for test:%s", tc.name)
|
||||
|
||||
+6
-1
@@ -500,12 +500,17 @@ func (c *containerMounter) getMountNameAndOptionsVFS2(conf *config.Config, m *mo
|
||||
|
||||
// Find filesystem name and FS specific data field.
|
||||
switch m.mount.Type {
|
||||
case devpts.Name, devtmpfs.Name, proc.Name, sys.Name:
|
||||
case devpts.Name, devtmpfs.Name, proc.Name:
|
||||
// Nothing to do.
|
||||
|
||||
case nonefs:
|
||||
fsName = sys.Name
|
||||
|
||||
case sys.Name:
|
||||
if len(c.productName) > 0 {
|
||||
internalData = &sys.InternalData{ProductName: c.productName}
|
||||
}
|
||||
|
||||
case tmpfs.Name:
|
||||
var err error
|
||||
data, err = parseAndFilterOptions(m.mount.Options, tmpfsAllowedData...)
|
||||
|
||||
+31
-7
@@ -16,6 +16,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
@@ -107,6 +108,10 @@ type Boot struct {
|
||||
// terminates. This flag is set when the command execve's itself because
|
||||
// parent death signal doesn't propagate through execve when uid/gid changes.
|
||||
attached bool
|
||||
|
||||
// productName is the value to show in
|
||||
// /sys/devices/virtual/dmi/id/product_name.
|
||||
productName string
|
||||
}
|
||||
|
||||
// Name implements subcommands.Command.Name.
|
||||
@@ -146,6 +151,7 @@ func (b *Boot) SetFlags(f *flag.FlagSet) {
|
||||
f.IntVar(&b.profileMutexFD, "profile-mutex-fd", -1, "file descriptor to write mutex profile to. -1 disables profiling.")
|
||||
f.IntVar(&b.traceFD, "trace-fd", -1, "file descriptor to write Go execution trace to. -1 disables tracing.")
|
||||
f.BoolVar(&b.attached, "attached", false, "if attached is true, kills the sandbox process when the parent process terminates")
|
||||
f.StringVar(&b.productName, "product-name", "", "value to show in /sys/devices/virtual/dmi/id/product_name")
|
||||
}
|
||||
|
||||
// Execute implements subcommands.Command.Execute. It starts a sandbox in a
|
||||
@@ -161,6 +167,16 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
|
||||
// Set traceback level
|
||||
debug.SetTraceback(conf.Traceback)
|
||||
|
||||
if len(b.productName) == 0 {
|
||||
// Do this before chroot takes effect, otherwise we can't read /sys.
|
||||
if product, err := ioutil.ReadFile("/sys/devices/virtual/dmi/id/product_name"); err != nil {
|
||||
log.Warningf("Not setting product_name: %v", err)
|
||||
} else {
|
||||
b.productName = strings.TrimSpace(string(product))
|
||||
log.Infof("Setting product_name: %q", b.productName)
|
||||
}
|
||||
}
|
||||
|
||||
if b.attached {
|
||||
// Ensure this process is killed after parent process terminates when
|
||||
// attached mode is enabled. In the unfortunate event that the parent
|
||||
@@ -177,7 +193,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
|
||||
|
||||
if !b.applyCaps && !conf.Rootless {
|
||||
// Remove --apply-caps arg to call myself. It has already been done.
|
||||
args := prepareArgs(b.attached, "setup-root")
|
||||
args := b.prepareArgs("setup-root")
|
||||
|
||||
// Note that we've already read the spec from the spec FD, and
|
||||
// we will read it again after the exec call. This works
|
||||
@@ -217,7 +233,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
|
||||
|
||||
// Remove --apply-caps and --setup-root arg to call myself. Both have
|
||||
// already been done.
|
||||
args := prepareArgs(b.attached, "setup-root", "apply-caps")
|
||||
args := b.prepareArgs("setup-root", "apply-caps")
|
||||
|
||||
// Note that we've already read the spec from the spec FD, and
|
||||
// we will read it again after the exec call. This works
|
||||
@@ -271,6 +287,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
|
||||
ProfileHeapFD: b.profileHeapFD,
|
||||
ProfileMutexFD: b.profileMutexFD,
|
||||
TraceFD: b.traceFD,
|
||||
ProductName: b.productName,
|
||||
}
|
||||
l, err := boot.New(bootArgs)
|
||||
if err != nil {
|
||||
@@ -308,7 +325,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
func prepareArgs(attached bool, exclude ...string) []string {
|
||||
func (b *Boot) prepareArgs(exclude ...string) []string {
|
||||
var args []string
|
||||
for _, arg := range os.Args {
|
||||
for _, excl := range exclude {
|
||||
@@ -317,10 +334,17 @@ func prepareArgs(attached bool, exclude ...string) []string {
|
||||
}
|
||||
}
|
||||
args = append(args, arg)
|
||||
if attached && arg == "boot" {
|
||||
// Strategicaly place "--attached" after the command. This is needed
|
||||
// to ensure the new process is killed when the parent process terminates.
|
||||
args = append(args, "--attached")
|
||||
// Strategically add parameters after the command and before the container
|
||||
// ID at the end.
|
||||
if arg == "boot" {
|
||||
if b.attached {
|
||||
// This is needed to ensure the new process is killed when the parent
|
||||
// process terminates.
|
||||
args = append(args, "--attached")
|
||||
}
|
||||
if len(b.productName) > 0 {
|
||||
args = append(args, "--product-name", b.productName)
|
||||
}
|
||||
}
|
||||
skip:
|
||||
}
|
||||
|
||||
@@ -798,3 +798,23 @@ func TestDeleteInterface(t *testing.T) {
|
||||
t.Fatalf("loopback interface is removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProductName(t *testing.T) {
|
||||
want, err := ioutil.ReadFile("/sys/devices/virtual/dmi/id/product_name")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
d := dockerutil.MakeContainer(ctx, t)
|
||||
defer d.CleanUp(ctx)
|
||||
|
||||
opts := dockerutil.RunOpts{Image: "basic/alpine"}
|
||||
got, err := d.Run(ctx, opts, "cat", "/sys/devices/virtual/dmi/id/product_name")
|
||||
if err != nil {
|
||||
t.Fatalf("docker run failed: %v", err)
|
||||
}
|
||||
if string(want) != got {
|
||||
t.Errorf("invalid product name, want: %q, got: %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user