blob: 1a8a9e60641f2a920b5f5bd92bde0f6cf57b2c91 [file]
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package security
import (
"bytes"
"context"
"os/user"
"chromiumos/tast/common/testexec"
"chromiumos/tast/errors"
"chromiumos/tast/local/sysutil"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: SafesetidEnsurePolicy,
Desc: "Runs SafeSetID though various example ID transitions",
Contacts: []string{
"thomascedeno@google.com",
"mortonm@google.com",
},
SoftwareDeps: []string{},
Attr: []string{"group:mainline", "informational"},
})
}
// SafesetidEnsurePolicy forks processes as non-root users and ensures the processes
// can change UID to a user that is explicitly allowed in the system-wide allowlist, but no
// other user.
func SafesetidEnsurePolicy(ctx context.Context, s *testing.State) {
// Fetch kernel version for later runtime check.
ver, _, err := sysutil.KernelVersionAndArch()
if err != nil {
s.Fatal("Failed to get kernel version: ", err)
}
// Need to ensure that all the users in our test exist or have been initialized properly.
err = checkUsersandGroupsExist()
if err != nil {
s.Fatalf("%v", err)
}
// Need to check for kernel version 5.10, as safesetid for GID's relies on functionality from that version.
testGIDEnabled := false
if ver.IsOrLater(5, 10) {
testGIDEnabled = true
} else {
s.Logf("Kernel version is too old for Group ID support, (%v), safesetid needs (5,10)", ver)
}
// Main test starts here, looping through users and testing uid and gid transitions.
for _, tc := range []struct {
parent string
child string
capSETUID bool
expectSuccess bool
}{
{"cros-disks", "chronos", true, true},
{"cros-disks", "fuse-exfat", true, true},
{"cros-disks", "fuse-sshfs", true, true},
{"cros-disks", "nobody", true, true},
{"cros-disks", "ntfs-3g", true, true},
{"cros-disks", "fuse-rar2fs", true, true},
{"cros-disks", "fuse-zip", true, true},
{"cros-disks", "chronos", false, false},
{"cros-disks", "fuse-exfat", false, false},
{"cros-disks", "fuse-sshfs", false, false},
{"cros-disks", "nobody", false, false},
{"cros-disks", "ntfs-3g", false, false},
{"cros-disks", "fuse-rar2fs", false, false},
{"cros-disks", "fuse-zip", false, false},
{"shill", "nobody", true, true},
{"shill", "vpn", true, true},
{"shill", "syslog", true, true},
{"shill", "dhcp", true, true},
{"shill", "dhcp", false, false},
{"shill", "vpn", false, false},
{"shill", "syslog", false, false},
{"shill", "nobody", false, false},
{"cros-disks", "root", true, false},
{"shill", "chronos", true, false},
{"vpn", "root", true, false},
} {
err := transitionSetID(ctx, tc.parent, tc.child, tc.capSETUID, tc.expectSuccess, true, s)
if err != nil {
s.Errorf(" %v unable to setuid to %v with error: %v", tc.parent, tc.child, err)
}
if testGIDEnabled {
err = transitionSetID(ctx, tc.parent, tc.child, tc.capSETUID, tc.expectSuccess, false, s)
if err != nil {
s.Errorf(" %v unable to setgid to %v with error: %v", tc.parent, tc.child, err)
}
}
}
}
func transitionSetID(ctx context.Context, parent, child string, giveCapSetID, expectSuccess, isUID bool, s *testing.State) error {
var caps string
var newGroup string
if giveCapSetID {
caps = "0xc0"
} else {
caps = "0x0"
}
if isUID { //UID case
newGroup = parent
} else { // GID case
newGroup = child
}
cmd := testexec.CommandContext(
ctx,
"/sbin/minijail0",
"-u",
parent,
"-g",
newGroup,
"-c",
caps,
"--",
"/sbin/capsh",
"--user="+child,
"--",
"-c",
"/usr/bin/whoami")
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
if expectSuccess {
return errors.Wrap(err, stderr.String())
}
return nil
}
if expectSuccess == false {
return errors.Errorf("%q allowed to transition without permission to %q", parent, child)
}
return nil
}
func checkUsersandGroupsExist() error {
// List of all users used in this program.
usersList := []user.User{
{Name: "cros-disks", Uid: "213", Gid: "213", HomeDir: "/dev/null"},
{Name: "chronos", Uid: "1000", Gid: "1000", HomeDir: "/home/chronos/user"},
{Name: "fuse-exfat", Uid: "302", Gid: "302", HomeDir: "/dev/null"},
{Name: "fuse-sshfs", Uid: "305", Gid: "305", HomeDir: "/dev/null"},
{Name: "fuse-rar2fs", Uid: "308", Gid: "308", HomeDir: "/dev/null"},
{Name: "fuse-smbfs", Uid: "307", Gid: "307", HomeDir: "/dev/null"},
{Name: "fuse-zip", Uid: "309", Gid: "309", HomeDir: "/dev/null"},
{Name: "ntfs-3g", Uid: "300", Gid: "300", HomeDir: "/dev/null"},
{Name: "nobody", Uid: "65534", Gid: "65534", HomeDir: "/dev/null"},
{Name: "vpn", Uid: "212", Gid: "212", HomeDir: "/dev/null"},
{Name: "syslog", Uid: "202", Gid: "202", HomeDir: "/dev/null"},
{Name: "dhcp", Uid: "224", Gid: "224", HomeDir: "/dev/null"},
}
for _, userProfile := range usersList {
_, errUser := user.Lookup(userProfile.Name)
if errUser != nil {
return errors.Wrap(errUser, userProfile.Name)
}
_, errGroup := user.LookupGroup(userProfile.Name)
if errGroup != nil {
return errors.Wrap(errGroup, userProfile.Name)
}
}
return nil
}