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:
Fabricio Voznika
2022-03-17 14:50:39 -07:00
committed by gVisor bot
parent 334090575d
commit 49896299a1
8 changed files with 133 additions and 29 deletions
+52 -10
View File
@@ -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
}
+1 -1
View File
@@ -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
View File
@@ -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,
}
}
+9 -1
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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:
}
+20
View File
@@ -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)
}
}