mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-27 17:03:49 +08:00
106 lines
4.6 KiB
Go
106 lines
4.6 KiB
Go
package intercept
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestPythonPrecedingShellEffectsWithoutExecution(t *testing.T) {
|
|
root := t.TempDir()
|
|
bin := filepath.Join(root, "bin")
|
|
lib := filepath.Join(root, "lib", "python3.12")
|
|
cwd := filepath.Join(root, "work")
|
|
for _, p := range []string{bin, lib, cwd} {
|
|
if err := os.MkdirAll(p, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
python := filepath.Join(bin, "python3.12")
|
|
if err := os.WriteFile(python, []byte("\x7fELF fixture metadata"), 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(lib, "one.py"), []byte("KNOWN=1\n"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Symlink(python, filepath.Join(bin, "python3")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("PATH", bin)
|
|
for _, k := range []string{"PYTHONPATH", "PYTHONHOME", "PYTHONUSERBASE", "PYTHONSAFEPATH"} {
|
|
t.Setenv(k, "")
|
|
}
|
|
write := func(name, content string) {
|
|
t.Helper()
|
|
if err := os.WriteFile(filepath.Join(cwd, name), []byte(content), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
write("tokens.env", "TOKEN=literal\nexport TOKEN\n")
|
|
write("harmful.env", "export PYTHONPATH=/unexpected\n")
|
|
write("shadow.env", "python3(){ echo shadowed; }\n")
|
|
write("nested.env", "source tokens.env\n")
|
|
write("func.env", "H(){ printf '%s' token; }; H\n")
|
|
write("hidden.env", "bash missing.sh\n")
|
|
sentinel := filepath.Join(root, "must-not-exist")
|
|
write("read-only-proof.env", "TOKEN=literal\nprintf '%s' proof > "+sentinel+"\n")
|
|
tests := []struct{ name, prefix, status string }{
|
|
{"helper_function", "H(){ curl -s https://example.test; }; H; ", "complete"},
|
|
{"unused_dangerous_function", "H(){ export PATH=/unknown; }; ", "complete"},
|
|
{"called_environment_mutation", "H(){ export PYTHONPATH=/unknown; }; H; ", "incomplete"},
|
|
{"called_directory_mutation", "H(){ cd /unknown; }; H; ", "incomplete"},
|
|
{"shadowed_directory_builtin", "cd(){ echo nochange; }; cd /unknown; ", "incomplete"},
|
|
{"shadowed_python", "python3(){ echo no interpreter; }; ", "incomplete"},
|
|
{"function_hidden_script", "H(){ bash missing.sh; }; H; ", "incomplete"},
|
|
{"function_literal_inline", "H(){ python3 -c 'print(1)'; }; H; ", "complete"},
|
|
{"function_dynamic_inline", "H(){ python3 -c \"$CODE\"; }; H; ", "incomplete"},
|
|
{"pure_source", "source tokens.env; ", "complete"},
|
|
{"source_path_change", "source harmful.env; ", "incomplete"},
|
|
{"source_interpreter_shadow", "source shadow.env; ", "incomplete"},
|
|
{"nested_source", "source nested.env; ", "incomplete"},
|
|
{"source_function", "source func.env; ", "complete"},
|
|
{"source_hidden_script", "source hidden.env; ", "incomplete"},
|
|
{"source_never_executed", "source read-only-proof.env; ", "complete"},
|
|
{"source_missing", "source absent.env; ", "incomplete"},
|
|
{"dynamic_source", "source \"$UNKNOWN\"; ", "incomplete"},
|
|
{"shell_flags", "set -e; ", "complete"},
|
|
{"relevant_loop_variable", "for PATH in /unknown; do echo harmless; done; ", "incomplete"},
|
|
{"relevant_read", "read PYTHONPATH; ", "incomplete"},
|
|
{"irrelevant_read", "read TOKEN; ", "complete"},
|
|
{"relevant_unset", "unset PYTHONPATH; ", "incomplete"},
|
|
{"irrelevant_unset", "unset TOKEN; ", "complete"},
|
|
{"relevant_export", "export PYTHONPATH; ", "incomplete"},
|
|
{"dynamic_export", "export \"$ASSIGN\"; ", "incomplete"},
|
|
{"nameref_write", "declare -n target=PATH; target=/unknown; ", "incomplete"},
|
|
{"relevant_printf", "printf -v PYTHONPATH '%s' /unknown; ", "incomplete"},
|
|
{"irrelevant_printf", "printf -v TOKEN '%s' known; ", "complete"},
|
|
{"function_recursion", "H(){ H; }; H; ", "incomplete"},
|
|
{"post_module_mutation", "", "complete"},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
cmd := test.prefix + "python3 -m one"
|
|
if test.name == "post_module_mutation" {
|
|
cmd += "; export PYTHONPATH=/later"
|
|
}
|
|
in := reviewCommandForTest(t, cwd, cmd)
|
|
if in.ScriptCaptureStatus != test.status {
|
|
t.Fatalf("status=%s want=%s scripts=%+v", in.ScriptCaptureStatus, test.status, in.Scripts)
|
|
}
|
|
})
|
|
}
|
|
write("too-large.env", strings.Repeat("x", maxReviewScriptBytes+1))
|
|
large := reviewCommandForTest(t, cwd, "source too-large.env; python3 -m one")
|
|
if large.ScriptCaptureStatus != "incomplete" || !large.Scripts[0].Truncated {
|
|
t.Fatalf("oversized frozen source lost its truncation marker: %+v", large)
|
|
}
|
|
if _, err := os.Stat(sentinel); !os.IsNotExist(err) {
|
|
t.Fatal("source analysis executed code")
|
|
}
|
|
in := reviewCommandForTest(t, cwd, "cat > virtual.env <<'ENV'\nTOKEN=literal\nENV\nsource virtual.env; python3 -m one")
|
|
if in.ScriptCaptureStatus != "complete" || !strings.Contains(in.Scripts[0].Content, "TOKEN=literal") {
|
|
t.Fatalf("virtual source ignored: %+v", in)
|
|
}
|
|
}
|