blob: 5c40f112660379e80e27abb438e7abc1ccdc6b39 [file] [log] [blame]
// Copyright 2018 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 (
"context"
"fmt"
"os"
"path/filepath"
"time"
"chromiumos/tast/errors"
"chromiumos/tast/local/bundles/cros/security/fscaps"
"chromiumos/tast/local/moblab"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: PrivilegedFiles,
Desc: "Compares files' setuid/setgid bits and capabilities against a baseline",
Contacts: []string{
"jorgelo@chromium.org", // Security team
"chromeos-security@google.com",
},
Attr: []string{"group:mainline"},
Timeout: 5 * time.Minute,
})
}
func PrivilegedFiles(ctx context.Context, s *testing.State) {
// Directories that should not be examined.
skippedDirs := makeStringSet([]string{
"/proc",
"/dev",
"/sys",
"/run",
"/usr/local",
"/mnt/stateful_partition",
})
// Moblab runs some services in a docker container.
if moblab.IsMoblab() {
skippedDirs["/mnt/moblab/containers/docker"] = struct{}{}
}
// Files permitted to have the setuid and setgid bits set.
// No error is reported if these files are missing or don't have the bit.
setuidBaseline := makeStringSet([]string{
"/usr/bin/powerd_setuid_helper",
// TODO(b/202991326): Remove fusermount3.
"/usr/bin/fusermount3",
"/usr/bin/sudo",
"/usr/libexec/dbus-daemon-launch-helper",
// Some boards (e.g. betty, novato) are released only as VM images with
// _userdebug ARC++ images which have more binaries setuid than _user images.
// _userdebug images are required for adb to work out of the box and for mesa to
// use llvmpipe graphics software rendering in VMs (b/33072485).
"/opt/google/containers/android/rootfs/root/system/xbin/librank",
"/opt/google/containers/android/rootfs/root/system/xbin/procmem",
"/opt/google/containers/android/rootfs/root/system/xbin/procrank",
"/opt/google/containers/android/rootfs/root/system/xbin/su",
})
setgidBaseline := makeStringSet([]string{
// See setuidBaseline for context.
"/opt/google/containers/android/rootfs/root/system/xbin/librank",
"/opt/google/containers/android/rootfs/root/system/xbin/procmem",
"/opt/google/containers/android/rootfs/root/system/xbin/procrank",
})
// Additional files exist on moblab boards.
if moblab.IsMoblab() {
setuidBaseline["/usr/libexec/lxc/lxc-user-nic"] = struct{}{}
setgidBaseline["/usr/bin/screen-4.6.1"] = struct{}{}
}
// Files permitted to have Linux capabilities. No error is reported if these files
// are missing or have no capabilities, but if they exist and have capabilities, they
// must exactly match the ones specified here.
capsBaseline := map[string]fscaps.Caps{
"/bin/arping": {Effective: fscaps.NET_RAW, Permitted: fscaps.NET_RAW},
"/bin/ping": {Effective: fscaps.NET_RAW, Permitted: fscaps.NET_RAW},
"/bin/ping6": {Effective: fscaps.NET_RAW, Permitted: fscaps.NET_RAW},
"/opt/google/containers/android/rootfs/root/system/bin/logd": {
Effective: fscaps.SETGID | fscaps.AUDIT_CONTROL,
Permitted: fscaps.SETGID | fscaps.AUDIT_CONTROL,
},
"/opt/google/containers/android/rootfs/root/system/bin/run-as": {
Effective: fscaps.SETGID | fscaps.SETUID,
Permitted: fscaps.SETGID | fscaps.SETUID,
},
"/opt/google/containers/android/rootfs/root/system/bin/simpleperf_app_runner": {
Effective: fscaps.SETGID | fscaps.SETUID,
Permitted: fscaps.SETGID | fscaps.SETUID,
},
"/opt/google/containers/android/rootfs/root/system/bin/surfaceflinger": {
Effective: fscaps.SYS_NICE,
Permitted: fscaps.SYS_NICE,
},
"/opt/google/containers/android/rootfs/root/system/bin/webview_zygote32": {
Effective: fscaps.SETGID | fscaps.SETUID | fscaps.SETPCAP,
Permitted: fscaps.SETGID | fscaps.SETUID | fscaps.SETPCAP,
},
"/sbin/unix_chkpwd": {Effective: fscaps.DAC_OVERRIDE, Permitted: fscaps.DAC_OVERRIDE},
"/usr/bin/fusermount": {Effective: fscaps.SYS_ADMIN, Permitted: fscaps.SYS_ADMIN},
"/usr/sbin/dnsmasq": {
Effective: fscaps.NET_ADMIN | fscaps.NET_BIND_SERVICE | fscaps.NET_RAW,
Inheritable: fscaps.NET_ADMIN | fscaps.NET_BIND_SERVICE | fscaps.NET_RAW,
},
"/usr/sbin/hostapd": {
Effective: fscaps.NET_ADMIN | fscaps.NET_RAW,
Inheritable: fscaps.NET_ADMIN | fscaps.NET_RAW,
},
}
const maxErrors = 10 // (approximately) the maximum number of errors to report
numFiles := 0
var fileErrs []string // error messages to report later
walkErr := filepath.Walk("/", func(path string, fi os.FileInfo, err error) error {
if err != nil {
// Ignore errors, which typically indicate that a file or dir was removed mid-walk.
return nil
}
if _, ok := skippedDirs[path]; ok {
return filepath.SkipDir
}
if fi.IsDir() {
return nil
}
if fi.Mode()&os.ModeSetuid != 0 {
if _, ok := setuidBaseline[path]; !ok {
fileErrs = append(fileErrs, path+" has unexpected setuid bit")
}
}
if fi.Mode()&os.ModeSetgid != 0 {
if _, ok := setgidBaseline[path]; !ok {
fileErrs = append(fileErrs, path+" has unexpected setgid bit")
}
}
if caps, err := fscaps.GetCaps(path); os.IsNotExist(err) {
// This probably means that the file was removed.
} else if err != nil {
fileErrs = append(fileErrs, fmt.Sprintf("Failed to get capabilities for %v: %v", path, err))
} else if exp := capsBaseline[path]; !caps.Empty() && caps != exp {
fileErrs = append(fileErrs, fmt.Sprintf("%v has capabilities %v; want %v", path, caps, exp))
}
numFiles++
if len(fileErrs) > maxErrors {
return errors.New("too many errors")
}
return nil
})
s.Logf("Examined %d files", numFiles)
for _, msg := range fileErrs {
s.Error(msg)
}
if walkErr != nil {
s.Error("Failed walking filesystem: ", walkErr)
}
}
// makeStringSet converts strs to a set for faster lookup.
func makeStringSet(strs []string) map[string]struct{} {
m := make(map[string]struct{}, len(strs))
for _, s := range strs {
m[s] = struct{}{}
}
return m
}