| // Copyright 2019 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" |
| "debug/elf" |
| "os" |
| "path/filepath" |
| "syscall" |
| "time" |
| |
| "chromiumos/tast/common/testexec" |
| "chromiumos/tast/errors" |
| "chromiumos/tast/local/bundles/cros/security/toolchain" |
| "chromiumos/tast/testing" |
| ) |
| |
| func init() { |
| testing.AddTest(&testing.Test{ |
| Func: ToolchainOptions, |
| Desc: "Verifies that system ELF executables were compiled with a hardened toolchain", |
| Contacts: []string{ |
| "jorgelo@chromium.org", // Security team |
| "kathrelkeld@chromium.org", // Tast port author |
| "chromeos-security@google.com", |
| }, |
| SoftwareDeps: []string{"no_asan"}, |
| Attr: []string{"group:mainline"}, |
| Params: []testing.Param{ |
| { |
| Val: toolchain.CheckNormal, |
| }, |
| { |
| Name: "allowlist", |
| Val: toolchain.CheckAllowlist, |
| }, |
| { |
| Name: "dlc", |
| Val: toolchain.CheckNormalWithDLCs, |
| }, |
| }, |
| }) |
| } |
| |
| // Paths that will be pruned/ignored when searching for ELF files. |
| var prunePaths = []string{ |
| "/proc", |
| "/dev", |
| "/sys", |
| "/mnt/stateful_partition", |
| "/usr/local", |
| // There are files in /home (e.g. encrypted files that look |
| // like they have ELF headers) that cause false positives, |
| // and since that's noexec anyways, it should be skipped. |
| "/home", |
| "/opt/google/containers", |
| "/run/containers/android_*/root", |
| // Linux perftools saves debug symbol cache under $HOME/.debug (crbug.com/1004817#c9). |
| "/root/.debug", |
| // Skip vdso .so files which are built together with the kernel without RELRO |
| "/lib/modules/*/vdso", |
| "/lib/modules/*/vdso32", |
| } |
| |
| // File match strings which will be ignored when searching for ELF files. |
| var ignoreMatches = []string{ |
| "libstdc++.so.*", |
| "libgcc_s.so.*", |
| } |
| |
| // Allowed files for the BIND_NOW condition. |
| var nowAllowlist = []string{ |
| // FIXME: crbug.com/535032 |
| "/opt/google/chrome/nacl_helper_nonsfi", |
| |
| // Allowed in crbug.com/682434. |
| "/usr/lib64/conntrack-tools/ct_helper_*.so", |
| "/usr/lib/conntrack-tools/ct_helper_*.so", |
| "/usr/sbin/nfct", |
| } |
| |
| var relroAllowlist = []string{ |
| // FIXME: crbug.com/535032 |
| "/opt/google/chrome/nacl_helper_nonsfi", |
| } |
| |
| var pieAllowlist []string |
| |
| var textrelAllowlist []string |
| |
| var stackAllowlist []string |
| |
| var loadwxAllowlist []string |
| |
| // FIXME(b/194115264): re-enable once all pre-built binaries are linked against compiler-rt/libunwind. |
| // var libgccAllowlist = []string |
| |
| var libstdcAllowlist = []string{ |
| // Flash player |
| "/opt/google/chrome/pepper/libpepflashplayer.so", |
| |
| // Prebuilt hdcp driver binary from Intel. |
| "/usr/sbin/hdcpd", |
| // Prebuilt binaries installed by Intel Camera HAL on kabylake boards. |
| "/usr/lib64/libSkyCamAIC.so", |
| "/usr/lib64/libSkyCamAICKBL.so", |
| // Part of prebuilt driver binary used in Tegra boards. |
| "/usr/lib/libnvmmlite_video.so", |
| // Allowed in b/73422412. |
| "/opt/google/rta/rtanalytics_main", |
| } |
| |
| func loadDLCs(ctx context.Context) error { |
| criticalDLCs := []string{"pita"} |
| for _, dlc := range criticalDLCs { |
| metadataPath := filepath.Join("/opt/google/dlc", dlc) |
| // Load the DLC if metadata exists for it as it should be preloadable on test images. |
| if _, err := os.Stat(metadataPath); err == nil { |
| if err := testexec.CommandContext(ctx, "dlcservice_util", "--install", "--id="+dlc, "--omaha_url=").Run(testexec.DumpLogOnError); err != nil { |
| return errors.Wrapf(err, "failed in loading %s DLC", dlc) |
| } |
| testing.ContextLog(ctx, "Successfully loaded DLC: ", dlc) |
| } |
| } |
| return nil |
| } |
| |
| func ToolchainOptions(ctx context.Context, s *testing.State) { |
| mode := s.Param().(toolchain.CheckMode) |
| if mode == toolchain.CheckNormalWithDLCs { |
| // TODO(crbug.com/1077056): Remove once DLC provisioning is in place for F20. |
| // Also make this generic to any DLCs to be checked against, without exhausting the loopback devices. |
| if err := loadDLCs(ctx); err != nil { |
| s.Error("Loading DLC failed: ", err) |
| } |
| } |
| |
| var conds []*toolchain.ELFCondition |
| |
| // Condition: Verify non-static binaries have BIND_NOW in dynamic section. |
| conds = append(conds, toolchain.NewELFCondition(toolchain.NowVerify, nowAllowlist)) |
| |
| // Condition: Verify non-static binaries have RELRO program header. |
| conds = append(conds, toolchain.NewELFCondition(toolchain.RelroVerify, relroAllowlist)) |
| |
| // Condition: Verify non-static binaries are dynamic (built PIE). |
| conds = append(conds, toolchain.NewELFCondition(toolchain.PieVerify, pieAllowlist)) |
| |
| // Condition: Verify dynamic ELFs don't include TEXTRELs. |
| conds = append(conds, toolchain.NewELFCondition(toolchain.TextrelVerify, textrelAllowlist)) |
| |
| // Condition: Verify all binaries have non-exec STACK program header. |
| conds = append(conds, toolchain.NewELFCondition(toolchain.StackVerify, stackAllowlist)) |
| |
| // Condition: Verify no binaries have W+X LOAD program headers. |
| conds = append(conds, toolchain.NewELFCondition(toolchain.LoadwxVerify, loadwxAllowlist)) |
| |
| // Condition: Verify all binaries are not linked with libgcc_s.so. |
| // FIXME: re-enable once all pre-built binaries are linked against compiler-rt/libunwind. |
| // libgccVerify := toolchain.CreateNotLinkedVerify("libgcc_s.so*") |
| // conds = append(conds, toolchain.NewELFCondition(libgccVerify, libgccAllowlist)) |
| |
| // Condition: Verify all binaries are not linked with libstdc++.so. |
| libstdcVerify := toolchain.CreateNotLinkedVerify("libstdc++.so*") |
| conds = append(conds, toolchain.NewELFCondition(libstdcVerify, libstdcAllowlist)) |
| |
| err := filepath.Walk("/", func(path string, info os.FileInfo, err error) error { |
| if os.IsNotExist(err) { |
| // The file has been removed after listed in the parent directory. |
| return nil |
| } |
| if err != nil { |
| return err |
| } |
| if info.IsDir() { |
| // Skip matches from prunePaths. |
| for _, pp := range prunePaths { |
| m, err := filepath.Match(pp, path) |
| if err != nil { |
| s.Fatalf("Could not match %v to %v", path, pp) |
| } |
| if m { |
| return filepath.SkipDir |
| } |
| } |
| return nil |
| } |
| if !info.Mode().IsRegular() || info.Mode()&0111 == 0 { |
| // Skip non-regular and non-executable files. |
| return nil |
| } |
| // Skip ignored files from ignoreMatches. |
| for _, im := range ignoreMatches { |
| m, err := filepath.Match(im, info.Name()) |
| if err != nil { |
| s.Fatalf("Could not match %v to %v", path, im) |
| } |
| if m { |
| return nil |
| } |
| } |
| |
| ef, err := elf.Open(path) |
| if err != nil { |
| // Skip files that are not valid ELF files. |
| return nil |
| } |
| defer ef.Close() |
| |
| // Run all defined condition checks on this ELF file. |
| for _, c := range conds { |
| if err := c.CheckAndFilter(path, ef, mode); err != nil { |
| s.Error("Condition failure: ", err) |
| |
| // Print details of the offending file for debugging. |
| if fi, err := os.Stat(path); err != nil { |
| s.Logf("File info: %s: failed to stat: %v", path, err) |
| } else { |
| var ctime time.Time |
| if st, ok := fi.Sys().(*syscall.Stat_t); ok { |
| ctime = time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec)) |
| } |
| s.Logf("File info: %s: size=%d, mode=%o, ctime=%s, mtime=%s", |
| path, fi.Size(), fi.Mode(), ctime.Format(time.RFC3339Nano), |
| fi.ModTime().Format(time.RFC3339Nano)) |
| } |
| } |
| } |
| return nil |
| }) |
| if err != nil { |
| s.Error("Error walking path: ", err) |
| } |
| } |