2018-05-26 01:30:04 +02:00
// 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.
2022-05-06 12:42:05 +02:00
// This tool tries to prohibit access to tools on the system on which the build
// is run.
//
// The rationale is that if the build uses a binary that is not shipped in the
// source tree, it is unknowable which version of that binary will be installed
// and therefore the output of the build will be unpredictable. Therefore, we
// should make every effort to use only tools under our control.
//
// This is currently implemented by a "sandbox" that sets $PATH to a specific,
// single directory and creates a symlink for every binary in $PATH in it. That
// symlink will point to path_interposer, which then uses an embedded
// configuration to determine whether to allow access to the binary (in which
// case it calls the original executable) or not (in which case it fails). It
// can also optionally log invocations.
//
// This, of course, does not help if one invokes the tool in question with its
// full path.
2018-05-26 01:30:04 +02:00
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 )
}
exitCode , err := Main ( os . Stdout , os . Stderr , interposer , os . Args , mainOpts {
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 {
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 } ( )
}
2020-05-02 01:29:00 +02:00
if config . Error {
2018-05-26 01:30:04 +02:00
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
}