Merge "Revert "Revert "Add path interposer"""
This commit is contained in:
commit
bfb2b7e877
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
blueprint_go_binary {
|
||||
name: "path_interposer",
|
||||
deps: ["soong-ui-build-paths"],
|
||||
srcs: ["main.go"],
|
||||
testSrcs: ["main_test.go"],
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"android/soong/ui/build/paths"
|
||||
)
|
||||
|
||||
func main() {
|
||||
interposer, err := os.Executable()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Unable to locate interposer executable:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if fi, err := os.Lstat(interposer); err == nil {
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
link, err := os.Readlink(interposer)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Unable to read link to interposer executable:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if filepath.IsAbs(link) {
|
||||
interposer = link
|
||||
} else {
|
||||
interposer = filepath.Join(filepath.Dir(interposer), link)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, "Unable to stat interposer executable:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
disableError := false
|
||||
if e, ok := os.LookupEnv("TEMPORARY_DISABLE_PATH_RESTRICTIONS"); ok {
|
||||
disableError = e == "1" || e == "y" || e == "yes" || e == "on" || e == "true"
|
||||
}
|
||||
|
||||
exitCode, err := Main(os.Stdout, os.Stderr, interposer, os.Args, mainOpts{
|
||||
disableError: disableError,
|
||||
|
||||
sendLog: paths.SendLog,
|
||||
config: paths.GetConfig,
|
||||
lookupParents: lookupParents,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
}
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
var usage = fmt.Errorf(`To use the PATH interposer:
|
||||
* Write the original PATH variable to <interposer>_origpath
|
||||
* Set up a directory of symlinks to the PATH interposer, and use that in PATH
|
||||
|
||||
If a tool isn't in the allowed list, a log will be posted to the unix domain
|
||||
socket at <interposer>_log.`)
|
||||
|
||||
type mainOpts struct {
|
||||
disableError bool
|
||||
|
||||
sendLog func(logSocket string, entry *paths.LogEntry, done chan interface{})
|
||||
config func(name string) paths.PathConfig
|
||||
lookupParents func() []paths.LogProcess
|
||||
}
|
||||
|
||||
func Main(stdout, stderr io.Writer, interposer string, args []string, opts mainOpts) (int, error) {
|
||||
base := filepath.Base(args[0])
|
||||
|
||||
origPathFile := interposer + "_origpath"
|
||||
if base == filepath.Base(interposer) {
|
||||
return 1, usage
|
||||
}
|
||||
|
||||
origPath, err := ioutil.ReadFile(origPathFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return 1, usage
|
||||
} else {
|
||||
return 1, fmt.Errorf("Failed to read original PATH: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := &exec.Cmd{
|
||||
Args: args,
|
||||
Env: os.Environ(),
|
||||
|
||||
Stdin: os.Stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
}
|
||||
|
||||
if err := os.Setenv("PATH", string(origPath)); err != nil {
|
||||
return 1, fmt.Errorf("Failed to set PATH env: %v", err)
|
||||
}
|
||||
|
||||
if config := opts.config(base); config.Log || config.Error {
|
||||
var procs []paths.LogProcess
|
||||
if opts.lookupParents != nil {
|
||||
procs = opts.lookupParents()
|
||||
}
|
||||
|
||||
if opts.sendLog != nil {
|
||||
waitForLog := make(chan interface{})
|
||||
opts.sendLog(interposer+"_log", &paths.LogEntry{
|
||||
Basename: base,
|
||||
Args: args,
|
||||
Parents: procs,
|
||||
}, waitForLog)
|
||||
defer func() { <-waitForLog }()
|
||||
}
|
||||
if config.Error && !opts.disableError {
|
||||
return 1, fmt.Errorf("%q is not allowed to be used. See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.", base)
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Path, err = exec.LookPath(base)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
||||
if err = cmd.Run(); err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||
if status.Exited() {
|
||||
return status.ExitStatus(), nil
|
||||
} else if status.Signaled() {
|
||||
exitCode := 128 + int(status.Signal())
|
||||
return exitCode, nil
|
||||
} else {
|
||||
return 1, exitErr
|
||||
}
|
||||
} else {
|
||||
return 1, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
type procEntry struct {
|
||||
Pid int
|
||||
Ppid int
|
||||
Command string
|
||||
}
|
||||
|
||||
func readProcs() map[int]procEntry {
|
||||
cmd := exec.Command("ps", "-o", "pid,ppid,command")
|
||||
data, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return parseProcs(data)
|
||||
}
|
||||
|
||||
func parseProcs(data []byte) map[int]procEntry {
|
||||
lines := bytes.Split(data, []byte("\n"))
|
||||
if len(lines) < 2 {
|
||||
return nil
|
||||
}
|
||||
// Remove the header
|
||||
lines = lines[1:]
|
||||
|
||||
ret := make(map[int]procEntry, len(lines))
|
||||
for _, line := range lines {
|
||||
fields := bytes.SplitN(line, []byte(" "), 2)
|
||||
if len(fields) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
pid, err := strconv.Atoi(string(fields[0]))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
line = bytes.TrimLeft(fields[1], " ")
|
||||
|
||||
fields = bytes.SplitN(line, []byte(" "), 2)
|
||||
if len(fields) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
ppid, err := strconv.Atoi(string(fields[0]))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ret[pid] = procEntry{
|
||||
Pid: pid,
|
||||
Ppid: ppid,
|
||||
Command: string(bytes.TrimLeft(fields[1], " ")),
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func lookupParents() []paths.LogProcess {
|
||||
procs := readProcs()
|
||||
if procs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
list := []paths.LogProcess{}
|
||||
pid := os.Getpid()
|
||||
for {
|
||||
entry, ok := procs[pid]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
list = append([]paths.LogProcess{
|
||||
{
|
||||
Pid: pid,
|
||||
Command: entry.Command,
|
||||
},
|
||||
}, list...)
|
||||
|
||||
pid = entry.Ppid
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"android/soong/ui/build/paths"
|
||||
)
|
||||
|
||||
var tmpDir string
|
||||
var origPATH string
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
var err error
|
||||
tmpDir, err = ioutil.TempDir("", "interposer_test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
origPATH = os.Getenv("PATH")
|
||||
err = os.Setenv("PATH", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func setup(t *testing.T) string {
|
||||
f, err := ioutil.TempFile(tmpDir, "interposer")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = ioutil.WriteFile(f.Name()+"_origpath", []byte(origPATH), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return f.Name()
|
||||
}
|
||||
|
||||
func TestInterposer(t *testing.T) {
|
||||
interposer := setup(t)
|
||||
|
||||
logConfig := func(name string) paths.PathConfig {
|
||||
if name == "true" {
|
||||
return paths.PathConfig{
|
||||
Log: false,
|
||||
Error: false,
|
||||
}
|
||||
} else if name == "path_interposer_test_not_allowed" {
|
||||
return paths.PathConfig{
|
||||
Log: false,
|
||||
Error: true,
|
||||
}
|
||||
}
|
||||
return paths.PathConfig{
|
||||
Log: true,
|
||||
Error: false,
|
||||
}
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
|
||||
exitCode int
|
||||
err error
|
||||
logEntry string
|
||||
}{
|
||||
{
|
||||
name: "direct call",
|
||||
args: []string{interposer},
|
||||
|
||||
exitCode: 1,
|
||||
err: usage,
|
||||
},
|
||||
{
|
||||
name: "relative call",
|
||||
args: []string{filepath.Base(interposer)},
|
||||
|
||||
exitCode: 1,
|
||||
err: usage,
|
||||
},
|
||||
{
|
||||
name: "true",
|
||||
args: []string{"/my/path/true"},
|
||||
},
|
||||
{
|
||||
name: "relative true",
|
||||
args: []string{"true"},
|
||||
},
|
||||
{
|
||||
name: "exit code",
|
||||
args: []string{"bash", "-c", "exit 42"},
|
||||
|
||||
exitCode: 42,
|
||||
logEntry: "bash",
|
||||
},
|
||||
{
|
||||
name: "signal",
|
||||
args: []string{"bash", "-c", "kill -9 $$"},
|
||||
|
||||
exitCode: 137,
|
||||
logEntry: "bash",
|
||||
},
|
||||
{
|
||||
name: "does not exist",
|
||||
args: []string{"path_interposer_test_does_not_exist"},
|
||||
|
||||
exitCode: 1,
|
||||
err: fmt.Errorf(`exec: "path_interposer_test_does_not_exist": executable file not found in $PATH`),
|
||||
logEntry: "path_interposer_test_does_not_exist",
|
||||
},
|
||||
{
|
||||
name: "not allowed",
|
||||
args: []string{"path_interposer_test_not_allowed"},
|
||||
|
||||
exitCode: 1,
|
||||
err: fmt.Errorf(`"path_interposer_test_not_allowed" is not allowed to be used. See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.`),
|
||||
logEntry: "path_interposer_test_not_allowed",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
logged := false
|
||||
logFunc := func(logSocket string, entry *paths.LogEntry, done chan interface{}) {
|
||||
defer close(done)
|
||||
|
||||
logged = true
|
||||
if entry.Basename != testCase.logEntry {
|
||||
t.Errorf("unexpected log entry:\nwant: %q\n got: %q", testCase.logEntry, entry.Basename)
|
||||
}
|
||||
}
|
||||
|
||||
exitCode, err := Main(ioutil.Discard, ioutil.Discard, interposer, testCase.args, mainOpts{
|
||||
sendLog: logFunc,
|
||||
config: logConfig,
|
||||
})
|
||||
|
||||
errstr := func(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
return err.Error()
|
||||
}
|
||||
if errstr(testCase.err) != errstr(err) {
|
||||
t.Errorf("unexpected error:\nwant: %v\n got: %v", testCase.err, err)
|
||||
}
|
||||
if testCase.exitCode != exitCode {
|
||||
t.Errorf("expected exit code %d, got %d", testCase.exitCode, exitCode)
|
||||
}
|
||||
if !logged && testCase.logEntry != "" {
|
||||
t.Errorf("no log entry, but expected %q", testCase.logEntry)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingPath(t *testing.T) {
|
||||
interposer := setup(t)
|
||||
err := os.Remove(interposer + "_origpath")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to remove:", err)
|
||||
}
|
||||
|
||||
exitCode, err := Main(ioutil.Discard, ioutil.Discard, interposer, []string{"true"}, mainOpts{})
|
||||
if err != usage {
|
||||
t.Errorf("Unexpected error:\n got: %v\nwant: %v", err, usage)
|
||||
}
|
||||
if exitCode != 1 {
|
||||
t.Errorf("expected exit code %d, got %d", 1, exitCode)
|
||||
}
|
||||
}
|
|
@ -18,8 +18,6 @@ def SearchPathEnv(name):
|
|||
for directory in search_path:
|
||||
if directory == '': continue
|
||||
path = os.path.join(directory, name)
|
||||
if os.path.islink(path):
|
||||
path = os.path.realpath(path)
|
||||
# Check if path is actual executable file.
|
||||
if os.path.isfile(path) and os.access(path, os.X_OK):
|
||||
return path
|
||||
|
|
|
@ -12,10 +12,23 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
bootstrap_go_package {
|
||||
name: "soong-ui-build-paths",
|
||||
pkgPath: "android/soong/ui/build/paths",
|
||||
srcs: [
|
||||
"paths/config.go",
|
||||
"paths/logs.go",
|
||||
],
|
||||
testSrcs: [
|
||||
"paths/logs_test.go",
|
||||
],
|
||||
}
|
||||
|
||||
bootstrap_go_package {
|
||||
name: "soong-ui-build",
|
||||
pkgPath: "android/soong/ui/build",
|
||||
deps: [
|
||||
"soong-ui-build-paths",
|
||||
"soong-ui-logger",
|
||||
"soong-ui-tracer",
|
||||
"soong-shared",
|
||||
|
@ -33,6 +46,7 @@ bootstrap_go_package {
|
|||
"finder.go",
|
||||
"kati.go",
|
||||
"ninja.go",
|
||||
"path.go",
|
||||
"proc_sync.go",
|
||||
"signal.go",
|
||||
"soong.go",
|
||||
|
|
|
@ -140,6 +140,8 @@ func Build(ctx Context, config Config, what int) {
|
|||
|
||||
ensureEmptyDirectoriesExist(ctx, config.TempDir())
|
||||
|
||||
SetupPath(ctx, config)
|
||||
|
||||
if what&BuildProductConfig != 0 {
|
||||
// Run make for product config
|
||||
runMakeProductConfig(ctx, config)
|
||||
|
|
|
@ -51,6 +51,8 @@ type configImpl struct {
|
|||
targetDeviceDir string
|
||||
|
||||
brokenDupRules bool
|
||||
|
||||
pathReplaced bool
|
||||
}
|
||||
|
||||
const srcDirFileCheck = "build/soong/root.bp"
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/blueprint/microfactory"
|
||||
|
||||
"android/soong/ui/build/paths"
|
||||
)
|
||||
|
||||
func parsePathDir(dir string) []string {
|
||||
f, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if s, err := f.Stat(); err != nil || !s.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
infos, err := f.Readdir(-1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := make([]string, 0, len(infos))
|
||||
for _, info := range infos {
|
||||
if m := info.Mode(); !m.IsDir() && m&0111 != 0 {
|
||||
ret = append(ret, info.Name())
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func SetupPath(ctx Context, config Config) {
|
||||
if config.pathReplaced {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.BeginTrace("path")
|
||||
defer ctx.EndTrace()
|
||||
|
||||
origPath, _ := config.Environment().Get("PATH")
|
||||
myPath := filepath.Join(config.OutDir(), ".path")
|
||||
interposer := myPath + "_interposer"
|
||||
|
||||
var cfg microfactory.Config
|
||||
cfg.Map("android/soong", "build/soong")
|
||||
cfg.TrimPath, _ = filepath.Abs(".")
|
||||
if _, err := microfactory.Build(&cfg, interposer, "android/soong/cmd/path_interposer"); err != nil {
|
||||
ctx.Fatalln("Failed to build path interposer:", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil {
|
||||
ctx.Fatalln("Failed to write original path:", err)
|
||||
}
|
||||
|
||||
entries, err := paths.LogListener(ctx.Context, interposer+"_log")
|
||||
if err != nil {
|
||||
ctx.Fatalln("Failed to listen for path logs:", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for log := range entries {
|
||||
curPid := os.Getpid()
|
||||
for i, proc := range log.Parents {
|
||||
if proc.Pid == curPid {
|
||||
log.Parents = log.Parents[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
procPrints := []string{
|
||||
"See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.",
|
||||
}
|
||||
if len(log.Parents) > 0 {
|
||||
procPrints = append(procPrints, "Process tree:")
|
||||
for i, proc := range log.Parents {
|
||||
procPrints = append(procPrints, fmt.Sprintf("%s→ %s", strings.Repeat(" ", i), proc.Command))
|
||||
}
|
||||
}
|
||||
|
||||
config := paths.GetConfig(log.Basename)
|
||||
if config.Error {
|
||||
ctx.Printf("Disallowed PATH tool %q used: %#v", log.Basename, log.Args)
|
||||
for _, line := range procPrints {
|
||||
ctx.Println(line)
|
||||
}
|
||||
} else {
|
||||
ctx.Verbosef("Unknown PATH tool %q used: %#v", log.Basename, log.Args)
|
||||
for _, line := range procPrints {
|
||||
ctx.Verboseln(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
ensureEmptyDirectoriesExist(ctx, myPath)
|
||||
|
||||
var execs []string
|
||||
for _, pathEntry := range filepath.SplitList(origPath) {
|
||||
if pathEntry == "" {
|
||||
// Ignore the current directory
|
||||
continue
|
||||
}
|
||||
// TODO(dwillemsen): remove path entries under TOP? or anything
|
||||
// that looks like an android source dir? They won't exist on
|
||||
// the build servers, since they're added by envsetup.sh.
|
||||
// (Except for the JDK, which is configured in ui/build/config.go)
|
||||
|
||||
execs = append(execs, parsePathDir(pathEntry)...)
|
||||
}
|
||||
|
||||
allowAllSymlinks := config.Environment().IsEnvTrue("TEMPORARY_DISABLE_PATH_RESTRICTIONS")
|
||||
for _, name := range execs {
|
||||
if !paths.GetConfig(name).Symlink && !allowAllSymlinks {
|
||||
continue
|
||||
}
|
||||
|
||||
err := os.Symlink("../.path_interposer", filepath.Join(myPath, name))
|
||||
// Intentionally ignore existing files -- that means that we
|
||||
// just created it, and the first one should win.
|
||||
if err != nil && !os.IsExist(err) {
|
||||
ctx.Fatalln("Failed to create symlink:", err)
|
||||
}
|
||||
}
|
||||
|
||||
myPath, _ = filepath.Abs(myPath)
|
||||
config.Environment().Set("PATH", myPath)
|
||||
config.pathReplaced = true
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package paths
|
||||
|
||||
import "runtime"
|
||||
|
||||
type PathConfig struct {
|
||||
// Whether to create the symlink in the new PATH for this tool.
|
||||
Symlink bool
|
||||
|
||||
// Whether to log about usages of this tool to the soong.log
|
||||
Log bool
|
||||
|
||||
// Whether to exit with an error instead of invoking the underlying tool.
|
||||
Error bool
|
||||
}
|
||||
|
||||
var Allowed = PathConfig{
|
||||
Symlink: true,
|
||||
Log: false,
|
||||
Error: false,
|
||||
}
|
||||
|
||||
var Forbidden = PathConfig{
|
||||
Symlink: false,
|
||||
Log: true,
|
||||
Error: true,
|
||||
}
|
||||
|
||||
// The configuration used if the tool is not listed in the config below.
|
||||
// Currently this will create the symlink, but log a warning. In the future,
|
||||
// I expect this to move closer to Forbidden.
|
||||
var Missing = PathConfig{
|
||||
Symlink: true,
|
||||
Log: true,
|
||||
Error: false,
|
||||
}
|
||||
|
||||
func GetConfig(name string) PathConfig {
|
||||
if config, ok := Configuration[name]; ok {
|
||||
return config
|
||||
}
|
||||
return Missing
|
||||
}
|
||||
|
||||
var Configuration = map[string]PathConfig{
|
||||
"awk": Allowed,
|
||||
"basename": Allowed,
|
||||
"bash": Allowed,
|
||||
"bzip2": Allowed,
|
||||
"cat": Allowed,
|
||||
"chmod": Allowed,
|
||||
"cmp": Allowed,
|
||||
"comm": Allowed,
|
||||
"cp": Allowed,
|
||||
"cut": Allowed,
|
||||
"date": Allowed,
|
||||
"dd": Allowed,
|
||||
"diff": Allowed,
|
||||
"dirname": Allowed,
|
||||
"echo": Allowed,
|
||||
"egrep": Allowed,
|
||||
"env": Allowed,
|
||||
"expr": Allowed,
|
||||
"find": Allowed,
|
||||
"getconf": Allowed,
|
||||
"getopt": Allowed,
|
||||
"git": Allowed,
|
||||
"grep": Allowed,
|
||||
"gzip": Allowed,
|
||||
"head": Allowed,
|
||||
"hexdump": Allowed,
|
||||
"hostname": Allowed,
|
||||
"jar": Allowed,
|
||||
"java": Allowed,
|
||||
"javap": Allowed,
|
||||
"ln": Allowed,
|
||||
"ls": Allowed,
|
||||
"m4": Allowed,
|
||||
"make": Allowed,
|
||||
"md5sum": Allowed,
|
||||
"mkdir": Allowed,
|
||||
"mktemp": Allowed,
|
||||
"mv": Allowed,
|
||||
"openssl": Allowed,
|
||||
"patch": Allowed,
|
||||
"perl": Allowed,
|
||||
"pstree": Allowed,
|
||||
"python": Allowed,
|
||||
"python2.7": Allowed,
|
||||
"python3": Allowed,
|
||||
"readlink": Allowed,
|
||||
"realpath": Allowed,
|
||||
"rm": Allowed,
|
||||
"rsync": Allowed,
|
||||
"runalarm": Allowed,
|
||||
"sed": Allowed,
|
||||
"setsid": Allowed,
|
||||
"sh": Allowed,
|
||||
"sha256sum": Allowed,
|
||||
"sha512sum": Allowed,
|
||||
"sort": Allowed,
|
||||
"stat": Allowed,
|
||||
"sum": Allowed,
|
||||
"tar": Allowed,
|
||||
"tail": Allowed,
|
||||
"touch": Allowed,
|
||||
"tr": Allowed,
|
||||
"true": Allowed,
|
||||
"uname": Allowed,
|
||||
"uniq": Allowed,
|
||||
"unzip": Allowed,
|
||||
"wc": Allowed,
|
||||
"which": Allowed,
|
||||
"whoami": Allowed,
|
||||
"xargs": Allowed,
|
||||
"xmllint": Allowed,
|
||||
"xz": Allowed,
|
||||
"zip": Allowed,
|
||||
"zipinfo": Allowed,
|
||||
|
||||
// Host toolchain is removed. In-tree toolchain should be used instead.
|
||||
// GCC also can't find cc1 with this implementation.
|
||||
"ar": Forbidden,
|
||||
"as": Forbidden,
|
||||
"cc": Forbidden,
|
||||
"clang": Forbidden,
|
||||
"clang++": Forbidden,
|
||||
"gcc": Forbidden,
|
||||
"g++": Forbidden,
|
||||
"ld": Forbidden,
|
||||
"ld.bfd": Forbidden,
|
||||
"ld.gold": Forbidden,
|
||||
"pkg-config": Forbidden,
|
||||
|
||||
// We've got prebuilts of these
|
||||
//"dtc": Forbidden,
|
||||
//"lz4": Forbidden,
|
||||
//"lz4c": Forbidden,
|
||||
}
|
||||
|
||||
func init() {
|
||||
if runtime.GOOS == "darwin" {
|
||||
Configuration["md5"] = Allowed
|
||||
Configuration["sw_vers"] = Allowed
|
||||
Configuration["xcrun"] = Allowed
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package paths
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LogProcess struct {
|
||||
Pid int
|
||||
Command string
|
||||
}
|
||||
|
||||
type LogEntry struct {
|
||||
Basename string
|
||||
Args []string
|
||||
Parents []LogProcess
|
||||
}
|
||||
|
||||
const timeoutDuration = time.Duration(100) * time.Millisecond
|
||||
|
||||
type socketAddrFunc func(string) (string, func(), error)
|
||||
|
||||
func procFallback(name string) (string, func(), error) {
|
||||
d, err := os.Open(filepath.Dir(name))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("/proc/self/fd/%d/%s", d.Fd(), filepath.Base(name)), func() {
|
||||
d.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
func tmpFallback(name string) (addr string, cleanup func(), err error) {
|
||||
d, err := ioutil.TempDir("/tmp", "log_sock")
|
||||
if err != nil {
|
||||
cleanup = func() {}
|
||||
return
|
||||
}
|
||||
cleanup = func() {
|
||||
os.RemoveAll(d)
|
||||
}
|
||||
|
||||
dir := filepath.Dir(name)
|
||||
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Symlink(absDir, filepath.Join(d, "d"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addr = filepath.Join(d, "d", filepath.Base(name))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getSocketAddr(name string) (string, func(), error) {
|
||||
maxNameLen := len(syscall.RawSockaddrUnix{}.Path)
|
||||
|
||||
if len(name) < maxNameLen {
|
||||
return name, func() {}, nil
|
||||
}
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
addr, cleanup, err := procFallback(name)
|
||||
if err == nil {
|
||||
if len(addr) < maxNameLen {
|
||||
return addr, cleanup, nil
|
||||
}
|
||||
}
|
||||
cleanup()
|
||||
}
|
||||
|
||||
addr, cleanup, err := tmpFallback(name)
|
||||
if err == nil {
|
||||
if len(addr) < maxNameLen {
|
||||
return addr, cleanup, nil
|
||||
}
|
||||
}
|
||||
cleanup()
|
||||
|
||||
return name, func() {}, fmt.Errorf("Path to socket is still over size limit, fallbacks failed.")
|
||||
}
|
||||
|
||||
func dial(name string, lookup socketAddrFunc) (net.Conn, error) {
|
||||
socket, cleanup, err := lookup(name)
|
||||
defer cleanup()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{
|
||||
Timeout: timeoutDuration,
|
||||
}
|
||||
return dialer.Dial("unix", socket)
|
||||
}
|
||||
|
||||
func listen(name string, lookup socketAddrFunc) (net.Listener, error) {
|
||||
socket, cleanup, err := lookup(name)
|
||||
defer cleanup()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return net.Listen("unix", socket)
|
||||
}
|
||||
|
||||
func SendLog(logSocket string, entry *LogEntry, done chan interface{}) {
|
||||
sendLog(logSocket, getSocketAddr, entry, done)
|
||||
}
|
||||
|
||||
func sendLog(logSocket string, lookup socketAddrFunc, entry *LogEntry, done chan interface{}) {
|
||||
defer close(done)
|
||||
|
||||
conn, err := dial(logSocket, lookup)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
conn.SetDeadline(time.Now().Add(timeoutDuration))
|
||||
|
||||
enc := gob.NewEncoder(conn)
|
||||
enc.Encode(entry)
|
||||
}
|
||||
|
||||
func LogListener(ctx context.Context, logSocket string) (chan *LogEntry, error) {
|
||||
return logListener(ctx, logSocket, getSocketAddr)
|
||||
}
|
||||
|
||||
func logListener(ctx context.Context, logSocket string, lookup socketAddrFunc) (chan *LogEntry, error) {
|
||||
ret := make(chan *LogEntry, 5)
|
||||
|
||||
if err := os.Remove(logSocket); err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ln, err := listen(logSocket, lookup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
ln.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer close(ret)
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
ln.Close()
|
||||
break
|
||||
}
|
||||
conn.SetDeadline(time.Now().Add(timeoutDuration))
|
||||
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
|
||||
dec := gob.NewDecoder(conn)
|
||||
entry := &LogEntry{}
|
||||
if err := dec.Decode(entry); err != nil {
|
||||
return
|
||||
}
|
||||
ret <- entry
|
||||
}()
|
||||
}
|
||||
}()
|
||||
return ret, nil
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package paths
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSendLog(t *testing.T) {
|
||||
t.Run("Short name", func(t *testing.T) {
|
||||
d, err := ioutil.TempDir("", "s")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(d)
|
||||
f := filepath.Join(d, "s")
|
||||
|
||||
testSendLog(t, f, getSocketAddr)
|
||||
})
|
||||
|
||||
testLongName := func(t *testing.T, lookup socketAddrFunc) {
|
||||
d, err := ioutil.TempDir("", strings.Repeat("s", 150))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(d)
|
||||
f := filepath.Join(d, strings.Repeat("s", 10))
|
||||
|
||||
testSendLog(t, f, lookup)
|
||||
}
|
||||
|
||||
// Using a name longer than the ~100 limit of the underlying calls to bind, etc
|
||||
t.Run("Long name", func(t *testing.T) {
|
||||
testLongName(t, getSocketAddr)
|
||||
})
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
t.Run("Long name proc fallback", func(t *testing.T) {
|
||||
testLongName(t, procFallback)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Long name tmp fallback", func(t *testing.T) {
|
||||
testLongName(t, tmpFallback)
|
||||
})
|
||||
}
|
||||
|
||||
func testSendLog(t *testing.T, socket string, lookup socketAddrFunc) {
|
||||
ctx, _ := context.WithTimeout(context.Background(), 2*timeoutDuration)
|
||||
|
||||
recv, err := logListener(ctx, socket, lookup)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
sendLog(socket, lookup, &LogEntry{
|
||||
Basename: "test",
|
||||
Args: []string{"foo", "bar"},
|
||||
}, make(chan interface{}))
|
||||
}
|
||||
}()
|
||||
|
||||
count := 0
|
||||
for {
|
||||
select {
|
||||
case entry := <-recv:
|
||||
ref := LogEntry{
|
||||
Basename: "test",
|
||||
Args: []string{"foo", "bar"},
|
||||
}
|
||||
if !reflect.DeepEqual(ref, *entry) {
|
||||
t.Fatalf("Bad log entry: %v", entry)
|
||||
}
|
||||
count++
|
||||
|
||||
if count == 10 {
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Error("Hit timeout before receiving all logs")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendLogError(t *testing.T) {
|
||||
d, err := ioutil.TempDir("", "log_socket")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
t.Run("Missing file", func(t *testing.T) {
|
||||
start := time.Now()
|
||||
SendLog(filepath.Join(d, "missing"), &LogEntry{}, make(chan interface{}))
|
||||
elapsed := time.Since(start)
|
||||
if elapsed > timeoutDuration {
|
||||
t.Errorf("Should have been << timeout (%s), but was %s", timeoutDuration, elapsed)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Regular file", func(t *testing.T) {
|
||||
f := filepath.Join(d, "file")
|
||||
if fp, err := os.Create(f); err == nil {
|
||||
fp.Close()
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
SendLog(f, &LogEntry{}, make(chan interface{}))
|
||||
elapsed := time.Since(start)
|
||||
if elapsed > timeoutDuration {
|
||||
t.Errorf("Should have been << timeout (%s), but was %s", timeoutDuration, elapsed)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Reader not reading", func(t *testing.T) {
|
||||
f := filepath.Join(d, "sock1")
|
||||
|
||||
ln, err := net.Listen("unix", f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
done := make(chan bool, 1)
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
SendLog(f, &LogEntry{
|
||||
// Ensure a relatively large payload
|
||||
Basename: strings.Repeat(" ", 100000),
|
||||
}, make(chan interface{}))
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
break
|
||||
case <-time.After(12 * timeoutDuration):
|
||||
t.Error("Should have finished")
|
||||
}
|
||||
})
|
||||
}
|
|
@ -35,6 +35,12 @@
|
|||
(global-name-regex #"^com\.apple\.distributed_notifications") ; xcodebuild in Soong
|
||||
)
|
||||
|
||||
; Allow suid /bin/ps to function
|
||||
(allow process-exec (literal "/bin/ps") (with no-sandbox))
|
||||
|
||||
; Allow path_interposer unix domain socket without logging
|
||||
(allow network-outbound (literal (string-append (param "OUT_DIR") "/.path_interposer_log")))
|
||||
|
||||
; Allow executing any file
|
||||
(allow process-exec*)
|
||||
(allow process-fork)
|
||||
|
|
Loading…
Reference in New Issue