blob: 19affa8d8f023474eee8c00e17deb75ba08e7751 [file]
// 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 selinux
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
selinux "github.com/opencontainers/selinux/go-selinux"
"chromiumos/tast/errors"
"chromiumos/tast/testing"
)
// FilterResult is returned by a FileLabelCheckFilter indicating how a file
// should be handled.
type FilterResult int
const (
// Skip indicates that the file should be skipped.
Skip FilterResult = iota
// Check indicates that the file's SELinux context should be checked.
Check
)
// FileLabelCheckFilter returns true if the file described by path
// and fi should be skipped. fi is never nil.
type FileLabelCheckFilter func(path string, fi os.FileInfo) (skipFile, skipSubdir FilterResult)
// IgnorePaths returns a FileLabelCheckFilter which allows the test to skip files
// or directories matching pathsToIgnore, including its subdirectory.
func IgnorePaths(pathsToIgnore []string) FileLabelCheckFilter {
return func(p string, _ os.FileInfo) (FilterResult, FilterResult) {
for _, path := range pathsToIgnore {
if p == path {
return Skip, Skip
}
}
return Check, Check
}
}
// IgnorePathsRegex returns a FileLabelCheckFilter which allows the test to
// skip files or directories matching pathsToIgnore, including its
// subdirectory.
func IgnorePathsRegex(pathsToIgnore []string) FileLabelCheckFilter {
var compiled []*regexp.Regexp
for _, path := range pathsToIgnore {
compiled = append(compiled, regexp.MustCompile(fmt.Sprintf("^%s$", path)))
}
return func(p string, _ os.FileInfo) (FilterResult, FilterResult) {
for _, pattern := range compiled {
if pattern.MatchString(p) {
return Skip, Skip
}
}
return Check, Check
}
}
// IgnorePathsButNotContents returns a FileLabelCheckFilter which allows the test
// to skip files matching pathsToIgnore, but not its subdirectory.
func IgnorePathsButNotContents(pathsToIgnore []string) FileLabelCheckFilter {
return func(p string, _ os.FileInfo) (FilterResult, FilterResult) {
for _, path := range pathsToIgnore {
if p == path {
return Skip, Check
}
}
return Check, Check
}
}
// IgnorePathButNotContents returns a FileLabelCheckFilter which allows the test
// to skip files matching pathsToIgnore, but not its subdirectory.
func IgnorePathButNotContents(pathToIgnore string) FileLabelCheckFilter {
return IgnorePathsButNotContents([]string{pathToIgnore})
}
// CheckAll returns (Check, Check) to let the test to check all files
func CheckAll(_ string, _ os.FileInfo) (FilterResult, FilterResult) { return Check, Check }
// InvertFilterSkipFile takes one filter and return a FileLabelCheckFilter which
// reverses the boolean value for skipFile.
func InvertFilterSkipFile(filter FileLabelCheckFilter) FileLabelCheckFilter {
return func(p string, fi os.FileInfo) (FilterResult, FilterResult) {
skipFile, skipSubdir := filter(p, fi)
if skipFile == Skip {
return Check, skipSubdir
}
return Skip, skipSubdir
}
}
// contextUnmatchError is returned by checkFileContext if the file context did
// not match with the expectation.
type contextUnmatchError struct {
*errors.E
}
// checkFileContext takes a path and a expected, and return an error
// if the context mismatch or unable to check context.
func checkFileContext(ctx context.Context, path string, expected *regexp.Regexp, log bool) error {
actual, err := selinux.FileLabel(path)
if err != nil {
// Don't wrap error so callers can use os.IsNotExist, etc to check file access error.
return err
}
if !expected.MatchString(actual) {
return &contextUnmatchError{E: errors.Errorf("got %q; want %q", actual, expected)}
}
if log {
testing.ContextLogf(ctx, "File %q has correct label %q", path, actual)
}
return nil
}
// CheckContextReq holds parameters given to CheckContext.
type CheckContextReq struct {
// Path is a file path to check.
Path string
// Expected is a regexp that should match with the SELinux context of files.
Expected *regexp.Regexp
// Recursive indicates whether to check child files recursively.
Recursive bool
// Filter is a function to filter files to check. It may not be nil.
Filter FileLabelCheckFilter
// IgnoreErrors indicates whether system call errors for Path should be
// ignored. If Recursive is true, IgnoreError is set to true for all child
// files recursively checked. This behavior is intentional to avoid typical
// race conditions on special file systems (like sysfs and procfs).
//
// IgnoreErrors ignores all errors, not only "harmless" ones like ENOENT and
// ENOTDIR. When accessing files in special file systems, they can return
// arbitrary error code such as EIO. It does not make sense to make SELinux
// tests fail by such errors since they are not directly related to what we
// want to test.
IgnoreErrors bool
// Log indicates whether to log successful checks.
Log bool
}
// CheckContext checks path to have selinux label match expected. Errors are
// passed through s.
func CheckContext(ctx context.Context, s *testing.State, req *CheckContextReq) {
fi, err := os.Lstat(req.Path)
if err != nil {
if !req.IgnoreErrors {
s.Errorf("Failed to stat %v: %v", req.Path, err)
}
return
}
skipFile, skipSubdir := req.Filter(req.Path, fi)
if skipFile == Check {
if err := checkFileContext(ctx, req.Path, req.Expected, req.Log); err != nil {
if _, ok := err.(*contextUnmatchError); ok || !req.IgnoreErrors {
s.Errorf("Failed file context check for %v: %v", req.Path, err)
}
}
}
if !fi.IsDir() || !req.Recursive || skipSubdir == Skip {
return
}
fis, err := ioutil.ReadDir(req.Path)
if err != nil {
if !req.IgnoreErrors {
s.Errorf("Failed to list directory %s: %s", req.Path, err)
}
return
}
for _, fi := range fis {
CheckContext(ctx, s, &CheckContextReq{
Path: filepath.Join(req.Path, fi.Name()),
Expected: req.Expected,
Recursive: req.Recursive,
Filter: req.Filter,
IgnoreErrors: true, // always ignore errors for child files
Log: req.Log,
})
}
}
// FileContextRegexp returns a regex to wrap given context with "^u:object_r:xxx:s0$".
func FileContextRegexp(context string) (*regexp.Regexp, error) {
return regexp.Compile("^u:object_r:" + context + ":s0$")
}
// GpuDevices returns the folder for gpuDevices, for testcases for non-sysfs
// files.
func GpuDevices() ([]string, error) {
var devices []string
renderDs, err := filepath.Glob("/sys/class/drm/renderD*")
if err != nil {
return devices, errors.Wrap(err, "unable to locate render devices")
}
var firstErr error
var errCnt int
for _, entryTree := range renderDs {
deviceReal, err := filepath.EvalSymlinks(filepath.Join(entryTree, "device"))
if err != nil {
if firstErr != nil {
firstErr = errors.Wrap(err, "unable to resolve absolute deviceReal")
}
errCnt++
continue
}
// entryTree may link to something looks like
// ../../devices/pci0000:00/0000:00:02.0/virtio0/drm/renderD128
// We only want the real device path.
deviceReal = strings.SplitN(deviceReal, "/virtio", 2)[0]
devices = append(devices, deviceReal)
}
if firstErr == nil {
return devices, nil
}
return devices, errors.Wrapf(firstErr, "%d errors have occurred, first error is:", errCnt)
}
// IIOSensorDevices returns the folder for cros-ec related iio devices. even
// with err, devices without errors are still returned.
func IIOSensorDevices() ([]string, error) {
var devices []string
trees, err := filepath.Glob("/sys/bus/iio/devices/iio:device*")
if err != nil {
return devices, errors.Wrap(err, "unable to locate iio devices")
}
var firstErr error
var errCnt int
for _, entry := range trees {
name, err := ioutil.ReadFile(filepath.Join(entry, "name"))
if err != nil {
if firstErr != nil {
firstErr = errors.Wrap(err, "unable to determine device name")
}
errCnt++
continue
}
deviceReal, err := filepath.EvalSymlinks(entry)
if err != nil {
if firstErr != nil {
firstErr = errors.Wrap(err, "failed to evaluate symlink for iio device")
}
errCnt++
continue
}
if strings.HasPrefix(string(name), "cros-ec") {
devices = append(devices, deviceReal)
}
}
if firstErr == nil {
return devices, nil
}
return devices, errors.Wrapf(firstErr, "%d errors have occurred, first error is:", errCnt)
}
// IIOSensorFilter returns pairs of FilterResult to check only files that
// should have cros_sensor_hal_sysfs labeled.
func IIOSensorFilter(p string, fi os.FileInfo) (skipFile, skipSubdir FilterResult) {
sensorFiles := map[string]bool{
"flush": true,
"frequency": true,
"hwfifo_flush": true,
"hwfifo_timeout": true,
"sampling_frequency": true,
"in_activity_still_change_falling_en": true,
}
ringFiles := map[string]bool{
"enable": true,
"length": true,
"current_trigger": true,
}
if sensorFiles[fi.Name()] {
return Check, Check
}
if strings.Contains(p, "cros-ec-ring") && ringFiles[fi.Name()] {
return Check, Check
}
return Skip, Check
}