├── policy.json
├── syscalls.txt
├── policy.yaml
├── secimport
├── backends
│ ├── common
│ │ ├── __init__.py
│ │ ├── instrumentation_backend.py
│ │ ├── utils.py
│ │ └── system_calls.py
│ ├── bpftrace_backend
│ │ ├── __init__.py
│ │ ├── filters
│ │ │ ├── networking.bt
│ │ │ ├── file_system.bt
│ │ │ ├── is_current_module_under_supervision.bt
│ │ │ └── processes.bt
│ │ ├── actions
│ │ │ ├── log_python_module_entry.bt
│ │ │ ├── log_python_module_exit.bt
│ │ │ ├── log_network.bt
│ │ │ ├── log_file_system.bt
│ │ │ ├── log_syscall.bt
│ │ │ ├── kill_process.bt
│ │ │ └── kill_on_processing.bt
│ │ ├── probes
│ │ │ └── module_syscalls_allowlist_template.bt
│ │ ├── new_template.bt
│ │ ├── default.yaml.template.bt
│ │ └── bpftrace_backend.py
│ └── dtrace_backend
│ │ ├── __init__.py
│ │ ├── filters
│ │ ├── networking.d
│ │ ├── file_system.d
│ │ ├── is_current_module_under_supervision.d
│ │ └── processes.d
│ │ ├── headers
│ │ └── destructive.d
│ │ ├── actions
│ │ ├── log_syscall.d
│ │ ├── log_network.d
│ │ ├── log_file_system.d
│ │ ├── log_python_module_entry.d
│ │ ├── log_python_module_exit.d
│ │ ├── kill_on_processing.d
│ │ └── kill_process.d
│ │ ├── generate_profile.d
│ │ ├── default.blocklist.template.d
│ │ ├── probes
│ │ └── module_syscalls_allowlist_template.d
│ │ ├── default.allowlist.template.d
│ │ ├── default.template.d
│ │ ├── default.yaml.template.d
│ │ ├── py_sandbox.d
│ │ └── dtrace_backend.py
├── __init__.py
└── sandbox_helper.py
├── examples
├── python_api
│ ├── example.py
│ ├── numpy_example_with_secure_import.py
│ ├── http_request_with_secure_import.py
│ ├── http_request.py
│ ├── malicious_with_secure_import.py
│ ├── example2.py
│ ├── malicious.py
│ └── numpy_example.py
└── cli
│ ├── ebpf
│ ├── requests_demo
│ │ ├── do_request.py
│ │ ├── syscalls.txt
│ │ ├── nsjail-seccomp-sandbox.sh
│ │ ├── README.md
│ │ ├── policy.json
│ │ └── policy.yaml
│ ├── create_sandbox_from_yaml_or_json_policy
│ │ ├── create_profile_from_yaml.sh
│ │ ├── create_profile_from_yaml.py
│ │ └── example.yaml
│ ├── fastapi
│ │ ├── fastapi_example.sh
│ │ ├── fastapi_main.py
│ │ ├── syscalls.txt
│ │ ├── nsjail-seccomp-sandbox.sh
│ │ ├── policy.json
│ │ └── policy.yaml
│ ├── disable_os_system_usage
│ │ └── example_detect_code_execution.sh
│ ├── forbid_specific_syscall_used_by_any_module
│ │ └── processing_sandbox.bt
│ └── torch_demo
│ │ └── pytorch_example.py
│ └── dtrace
│ ├── run_dtrace_example.sh
│ └── example.d
├── scripts
├── update_docs_table_of_contents.sh
├── run_sandbox.sh
├── run_tests.sh
└── check.sh
├── nsjail-seccomp-sandbox.sh
├── SECURITY.md
├── .pre-commit-config.yaml
├── .github
├── dependabot.yml
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── workflows
│ └── python-publish.yml
├── docker
├── run.sh
├── build.sh
├── docker
│ ├── setup.sh
│ └── Dockerfile
└── README.md
├── docs
├── MAC_OS_USERS.md
├── ROADMAP.md
├── CONTRIBUTING.md
├── CHANGELOG.md
├── TRACING_PROCESSES.md
├── FAQ.md
├── YAML_PROFILES.md
├── INSTALL.md
├── PERFORMANCE.md
├── CLI.md
└── EXAMPLES.md
├── LICENSE
├── tests
├── test_dtrace_backend.py
├── test_sandbox_helper.py
├── test_bpftrace_backend.py
└── test_secimport_cli.py
├── pyproject.toml
├── .gitignore
└── README.md
/policy.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/syscalls.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/policy.yaml:
--------------------------------------------------------------------------------
1 | modules: {}
2 |
--------------------------------------------------------------------------------
/secimport/backends/common/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/python_api/example.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | os.system("Hello World!")
4 |
--------------------------------------------------------------------------------
/scripts/update_docs_table_of_contents.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | doctoc README.md docs
4 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/filters/networking.d:
--------------------------------------------------------------------------------
1 | if (probefunc == "socket")
2 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/headers/destructive.d:
--------------------------------------------------------------------------------
1 | # pragma D option destructive
2 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/filters/networking.bt:
--------------------------------------------------------------------------------
1 | if (@sysname[args->id] == "socket")
2 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/actions/log_syscall.d:
--------------------------------------------------------------------------------
1 | printf("\n@%s from %s", probefunc, current_module_str);
2 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/requests_demo/do_request.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | print(requests.get("https://google.com").status_code)
4 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/filters/file_system.bt:
--------------------------------------------------------------------------------
1 | if (@sysname[args->id] == "open" || @sysname[args->id] == "write")
2 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/actions/log_python_module_entry.bt:
--------------------------------------------------------------------------------
1 | printf("entered %s, %s, depth=%d\n", str(arg0), str(arg1), @["depth"]);
2 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/actions/log_python_module_exit.bt:
--------------------------------------------------------------------------------
1 | printf("exited %s, %s, depth=%d\n", str(arg0), str(arg1), @["depth"]);
2 |
--------------------------------------------------------------------------------
/scripts/run_sandbox.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo "🚀 Welcome to secimport 🚀"
4 | /workspace/Python-3.11.8/python -m secimport.cli interactive
5 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/actions/log_network.bt:
--------------------------------------------------------------------------------
1 | printf("\t\t(NETWORKING): %s in python module %s\r\n", @sysname[args->id], arg1, @globals["current_module"]);
2 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/actions/log_network.d:
--------------------------------------------------------------------------------
1 | printf("\t\t(NETWORKING): %s(%d from thread %d in python module %s\r\n", probefunc, arg1, tid, current_module_str);
2 |
--------------------------------------------------------------------------------
/nsjail-seccomp-sandbox.sh:
--------------------------------------------------------------------------------
1 | nsjail -Ml -Mo --chroot / --port 8000 --user 99999 --group 99999 --seccomp_string 'ALLOW { } DEFAULT KILL' -- /opt/homebrew/opt/python@3.11/bin/python3.11 -i
--------------------------------------------------------------------------------
/secimport/backends/common/instrumentation_backend.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class InstrumentationBackend(Enum):
5 | DTRACE = "dtrace"
6 | EBPF = "ebpf" # bpftrace
7 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/actions/log_file_system.bt:
--------------------------------------------------------------------------------
1 | printf("\t\t(TOUCHING FILESYSTEM): %s(%d) in python module %s\r\n", @sysname[args->id], arg1, @globals["current_module"]);
2 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/actions/log_file_system.d:
--------------------------------------------------------------------------------
1 | printf("\t\t(TOUCHING FILESYSTEM): %s(%d) from thread %d in python module %s\r\n", probefunc, arg1, tid, current_module_str);
2 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/filters/file_system.d:
--------------------------------------------------------------------------------
1 | if (probefunc == "read" || probefunc == "open" || probefunc == "write" || probefunc == "ioctl" || probefunc == "fstat" || probefunc == "stat" || probefunc == "openat")
2 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/actions/log_syscall.bt:
--------------------------------------------------------------------------------
1 | printf("%s SYSCALL %ld %s depth=%d previous_module=%s current_module=%s \n", probe, args->id, @sysname[args->id], @["depth"], @globals["previous_module"], @globals["current_module"])
2 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/create_sandbox_from_yaml_or_json_policy/create_profile_from_yaml.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo "Generating a profile from example yaml..."
3 | python examples/yaml_template/create_profile_from_yaml.py secimport/profiles/example.yaml /tmp/example.d
4 |
--------------------------------------------------------------------------------
/examples/python_api/numpy_example_with_secure_import.py:
--------------------------------------------------------------------------------
1 | from secimport import secure_import
2 |
3 | example = secure_import("numpy_example")
4 |
5 |
6 | def main():
7 | example.main()
8 |
9 |
10 | if __name__ == "__main__":
11 | main()
12 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/filters/is_current_module_under_supervision.bt:
--------------------------------------------------------------------------------
1 | if(@entrypoints["###MODULE_NAME###"] != 0 && @["depth"] >= @entrypoints["###MODULE_NAME###"] && @entrypoints["###MODULE_NAME###"] >= @entrypoints[@globals["previous_module"]])
2 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/filters/is_current_module_under_supervision.d:
--------------------------------------------------------------------------------
1 | if(depth_matrix["###MODULE_NAME###"]!= 0 && self->depth >= depth_matrix["###MODULE_NAME###"] && depth_matrix["###MODULE_NAME###"] >= depth_matrix[latest_supervised_module])
2 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/actions/log_python_module_entry.d:
--------------------------------------------------------------------------------
1 | printf("\r\n%d %6d %10d %16s:%-4d %-8s %*s-> %s (stack depth=%d)", cpu, pid, this->delta,
2 | current_module_str, arg2, "func", self->depth * 4, "",
3 | copyinstr(arg1), self->depth);
4 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/actions/log_python_module_exit.d:
--------------------------------------------------------------------------------
1 | printf("\r\n%d %6d %10d %16s:%-4d %-8s %*s<- %s (stack depth=%d)", cpu, pid, this->delta,
2 | current_module_str, arg2, "func", self->depth * 4, "",
3 | copyinstr(arg1), self->depth);
4 |
--------------------------------------------------------------------------------
/examples/python_api/http_request_with_secure_import.py:
--------------------------------------------------------------------------------
1 | from secimport import secure_import
2 |
3 | http_request = secure_import(
4 | "http_request",
5 | allow_shells=False,
6 | allow_networking=False,
7 | )
8 |
9 | if __name__ == "__main__":
10 | http_request.invoke_http_request()
11 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | We recommend using the latest official stable release: https://github.com/avilum/secimport/releases
6 |
7 | ## Reporting a Vulnerability
8 |
9 | Open an issue using the issue template:
10 | https://github.com/avilum/secimport/issues
11 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v2.3.0
4 | hooks:
5 | - id: check-yaml
6 | - id: end-of-file-fixer
7 | - id: trailing-whitespace
8 | - repo: https://github.com/psf/black
9 | rev: 22.10.0
10 | hooks:
11 | - id: black
12 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/fastapi/fastapi_example.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 |
4 | echo "FastAPI Example"
5 | echo "Tracing the main application, hit CTRL+C/CTRL+D when you are done."
6 | /workspace/Python-3.11.8/python -m secimport.cli trace --entrypoint fastapi_main.py
7 | /workspace/Python-3.11.8/python -m secimport.cli build
8 | /workspace/Python-3.11.8/python -m secimport.cli run --entrypoint fastapi_main.py
9 |
--------------------------------------------------------------------------------
/examples/cli/dtrace/run_dtrace_example.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [ `whoami` != root ]; then
3 | echo Please run this script as root or using sudo.
4 | exit
5 | fi
6 |
7 | echo "Running the malicious module with dtrace..."
8 |
9 | # An example using dtrace as a sandbox
10 | pkill -9 dtrace
11 | PYTHONPATH=$(pwd):$PYTHONPATH dtrace -s $(pwd)/templates/py_sandbox.d -c "python $(pwd)/examples/malicious.py"
12 | pkill -9 dtrace
13 |
--------------------------------------------------------------------------------
/examples/python_api/http_request.py:
--------------------------------------------------------------------------------
1 | import urllib.request
2 |
3 |
4 | def invoke_http_request():
5 | print("(python user space): Invoking http request...")
6 | with urllib.request.urlopen("http://example.com/") as response:
7 | print("(python user space): Received HTTP response")
8 | html = response.read()
9 | print(html)
10 |
11 |
12 | if __name__ == "__main__":
13 | invoke_http_request()
14 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/actions/kill_process.bt:
--------------------------------------------------------------------------------
1 | printf("\nKILLING PROCESS %s\n", str(pid));
2 | printf("\t\tKILLING...\r\n");
3 | system("pkill -9 python"); // optional. Please use "bpftrace --unsafe" or remove this line.
4 | printf("\t\tKILLED.\r\n");
5 | exit(); // optional
6 |
--------------------------------------------------------------------------------
/examples/python_api/malicious_with_secure_import.py:
--------------------------------------------------------------------------------
1 | from secimport import secure_import
2 |
3 | # 'secure_import' don't interfere with other modules. os should work.
4 | import os
5 |
6 | malicious = secure_import("malicious")
7 |
8 | if __name__ == "__main__":
9 | os.system(
10 | 'echo "\nHello from os.system!\n Now we will try to execute the malicious module under supervision..."'
11 | )
12 | malicious.malicious()
13 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/actions/kill_on_processing.bt:
--------------------------------------------------------------------------------
1 | if (###DESTRUCTIVE###){
2 | printf("\nKILLING PROCESS %s - EXECUTED execve;\n", str(pid));
3 | printf("\t\tKILLING...\r\n");
4 | system("pkill -9 python"); // optional. Please use "bpftrace --unsafe" or remove this line.
5 | printf("\t\tKILLED.\r\n");
6 | exit(); // optional
7 | }
8 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/fastapi/fastapi_main.py:
--------------------------------------------------------------------------------
1 | #! /workspace/Python-3.11.8/python
2 |
3 |
4 | from fastapi import FastAPI
5 | import uvicorn
6 |
7 | app = FastAPI()
8 |
9 |
10 | @app.get("/")
11 | async def root():
12 | return {"message": "Hello World"}
13 |
14 |
15 | @app.get("/backdoor")
16 | async def new():
17 | import os
18 |
19 | os.system("ps")
20 |
21 |
22 | if __name__ == "__main__":
23 | uvicorn.run(app, host="0.0.0.0", port=8000)
24 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/actions/kill_on_processing.d:
--------------------------------------------------------------------------------
1 | printf("\t\t\t\tDETECTED SHELL, depth=%d sandboxed_depth=%d sandboxed_module=%s current_module=%s\r\n", self->depth, depth_matrix["###MODULE_NAME###"], "###MODULE_NAME###", _current_module_str);
2 | printf("\t\tTERMINATING shell...\r\n");
3 | ustack();
4 | stop();
5 | printf("\t\tKILLING...\r\n");
6 | system("\t\tkill -9 %d", pid);
7 | printf("\t\tKILLED.\r\n");
8 | exit(-1);
9 |
--------------------------------------------------------------------------------
/secimport/__init__.py:
--------------------------------------------------------------------------------
1 | from secimport.backends.common.instrumentation_backend import InstrumentationBackend
2 | from secimport.backends.common.utils import (
3 | build_module_sandbox_from_yaml_template,
4 | )
5 | from secimport.sandbox_helper import secure_import
6 |
7 | imports = secure_import
8 | generate_sandbox_from_yaml = build_module_sandbox_from_yaml_template
9 | __all__ = [
10 | "imports",
11 | "secure_import",
12 | "generate_sandbox_from_yaml",
13 | "InstrumentationBackend",
14 | ]
15 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/requests_demo/syscalls.txt:
--------------------------------------------------------------------------------
1 | access, arch_prctl, bind, brk, clock_gettime, close, connect, dup, epoll_create1, exit_group, fcntl, fstat, futex, getcwd, getdents64, getegid, geteuid, getgid, getpeername, getpid, getrandom, getsockname, getsockopt, gettid, getuid, ioctl, lseek, lstat, mmap, mprotect, munmap, openat, poll, pread, prlimit64, read, readlink, recvfrom, rt_sigaction, rt_sigprocmask, sendto, set_robust_list, set_tid_address, setsockopt, socket, stat, sysinfo, uname, write
2 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/filters/processes.d:
--------------------------------------------------------------------------------
1 | if (probefunc == "posix_spawn" ||
2 | probefunc == "posix_spawnp" ||
3 | probefunc == "clone" ||
4 | probefunc == "__clone2" ||
5 | probefunc == "clone3" ||
6 | probefunc == "fork" ||
7 | probefunc == "vfork" ||
8 | probefunc == "forkexec" ||
9 | probefunc == "execl" ||
10 | probefunc == "execlp" ||
11 | probefunc == "execle" ||
12 | probefunc == "execv" ||
13 | probefunc == "execvp")
14 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "pip" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "daily"
12 |
--------------------------------------------------------------------------------
/docker/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ "$PWD" =~ docker$ ]]
4 | then
5 | cd ..
6 | fi
7 |
8 |
9 | if [[ "$PWD" =~ secimport$ ]]
10 | then
11 | echo "Running temporary secimport container...";
12 | docker run --pid host --rm --name=secimport -p 8000:8000 --privileged -v "$(pwd)/secimport":"/workspace/secimport/" -v "$(pwd)/examples":"/workspace/examples/" -v "$(pwd)/scripts":"/workspace/scripts/" -v "$(pwd)/tests":"/workspace/tests/" -it secimport
13 | else
14 | echo "Please run this script from the secimport directory.";
15 | exit 1;
16 | fi
17 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/fastapi/syscalls.txt:
--------------------------------------------------------------------------------
1 | access, chdir, clone, create_module, delete_module, epoll_ctl_old, epoll_wait_old, fork, fremovexattr, futimesat, get_kernel_syms, getcwd, getdents, getrusage, gettimeofday, init_module, io_destroy, io_getevents, io_setup, iopl, kill, lremovexattr, mq_timedsend, mremap, personality, pipe, pipe2, pread, query_module, remap_file_pages, sched_getaffinity, semget, set_thread_area, setrlimit, shmget, shutdown, sysfs, sysinfo, time, timer_create, timer_delete, tkill, uname, uselib, vmsplice, wait4, writev
2 |
--------------------------------------------------------------------------------
/examples/python_api/example2.py:
--------------------------------------------------------------------------------
1 | from secimport import secure_import
2 |
3 | example = secure_import("example", allow_shells=False)
4 | print("Example imported successfully")
5 |
6 | # Expected output:
7 | """
8 | (root) sh-3.2# export PYTHONPATH=$(pwd):$(pwd)/examples:$(pwd):$PYTHONPATH
9 | (root) sh-3.2# python examples/production.py
10 | Successfully compiled dtrace profile: /tmp/.secimport/sandbox_example.d
11 | (running dtrace supervisor): sudo dtrace -q -s /tmp/.secimport/sandbox_example.d -p 6484 -o /tmp/.secimport/sandbox_example.log &2>/dev/null
12 | Killed: 9
13 | """
14 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/probes/module_syscalls_allowlist_template.bt:
--------------------------------------------------------------------------------
1 |
2 | if (@syscalls_filters[@globals["current_module"], @sysname[args->id]] != 0){
3 | printf("\n*SUPERVISED FLOW: syscall '%s' called in '%s' from '%s'; which entered at depth %d;\nThe supervised module is %s which entered the stack in depth %d;\r\n", @sysname[args->id], @globals["current_module"], "###MODULE_NAME###", @entrypoints["###MODULE_NAME###"], @latest_supervised_module, @entrypoints[@latest_supervised_module]);
4 | ###SUPERVISED_MODULES_FILTER###
5 | ###SUPERVISED_MODULES_ACTION###
6 | }
7 |
--------------------------------------------------------------------------------
/scripts/run_tests.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | echo "Running tests..."
3 | echo "#!/bin/bash" > /tmp/secimport
4 | echo "/workspace/Python-3.11.8/python -m secimport.cli" > /tmp/secimport && chmod +x /tmp/secimport
5 | export PATH=/tmp/:$PATH
6 | export alias secimport="/workspace/Python-3.11.8/python -m secimport.cli"
7 | cd /workspace
8 |
9 | /workspace/Python-3.11.8/python -m pip install coverage pytest && /workspace/Python-3.11.8/python -m coverage run -m pytest --ignore="tests/test_dtrace_backend.py" --ignore="tests/test_secimport_cli.py" tests && /workspace/Python-3.11.8/python -m coverage report -m --skip-empty --omit=\"*/tests/*\"
10 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/filters/processes.bt:
--------------------------------------------------------------------------------
1 | if (@sysname[args->id] == "posix_spawn" ||
2 | @sysname[args->id] == "posix_spawnp" ||
3 | @sysname[args->id] == "clone" ||
4 | @sysname[args->id] == "__clone2" ||
5 | @sysname[args->id] == "clone3" ||
6 | @sysname[args->id] == "fork" ||
7 | @sysname[args->id] == "vfork" ||
8 | @sysname[args->id] == "forkexec" ||
9 | @sysname[args->id] == "execl" ||
10 | @sysname[args->id] == "execlp" ||
11 | @sysname[args->id] == "execle" ||
12 | @sysname[args->id] == "execv" ||
13 | @sysname[args->id] == "execvp")
14 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/disable_os_system_usage/example_detect_code_execution.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # "--unsafe" is required to run system command for remediation.
4 | # If process termination on violoation is not needed,
5 | # You can remove this argument.
6 |
7 | echo "Starting secimport sandbox with python shell..."
8 | bpftrace -c "/workspace/Python-3.11.8/python -c __import__('os').system('ps')" -o sandbox.log sandbox.bt --unsafe
9 |
10 | # The process is killed becused we ran os.system inside our sandbox.
11 | # Watch the logs:
12 | less +G sandbox.log
13 | # OR:
14 | # tail -n 20 sandbox.log
15 | echo "The sandbox log is at ./sandbox.log"
16 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/requests_demo/nsjail-seccomp-sandbox.sh:
--------------------------------------------------------------------------------
1 | nsjail -Ml -Mo --chroot / --port 8000 --user 99999 --group 99999 --seccomp_string 'ALLOW { access, arch_prctl, bind, brk, clock_gettime, close, connect, dup, epoll_create1, exit_group, fcntl, fstat, futex, getcwd, getdents64, getegid, geteuid, getgid, getpeername, getpid, getrandom, getsockname, getsockopt, gettid, getuid, ioctl, lseek, lstat, mmap, mprotect, munmap, openat, poll, pread, prlimit64, read, readlink, recvfrom, rt_sigaction, rt_sigprocmask, sendto, set_robust_list, set_tid_address, setsockopt, socket, stat, sysinfo, uname, write } DEFAULT KILL' -- /workspace/Python-3.11.8/python -i
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/fastapi/nsjail-seccomp-sandbox.sh:
--------------------------------------------------------------------------------
1 | nsjail -Ml -Mo --chroot / --port 8000 --user 99999 --group 99999 --seccomp_string 'ALLOW { access, chdir, clone, create_module, delete_module, epoll_ctl_old, epoll_wait_old, fork, fremovexattr, futimesat, get_kernel_syms, getcwd, getdents, getrusage, gettimeofday, init_module, io_destroy, io_getevents, io_setup, iopl, kill, lremovexattr, mq_timedsend, mremap, personality, pipe, pipe2, pread, query_module, remap_file_pages, sched_getaffinity, semget, set_thread_area, setrlimit, shmget, shutdown, sysfs, sysinfo, time, timer_create, timer_delete, tkill, uname, uselib, vmsplice, wait4, writev } DEFAULT KILL' -- /workspace/Python-3.11.8/python -i
2 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/actions/kill_process.d:
--------------------------------------------------------------------------------
1 | ustack();
2 | printf("\t####################\r\n");
3 | printf("\t\tKilling process; depth=%d sandboxed_depth=%d module=%s\r\n", self->depth, depth_matrix[_current_module_str], _current_module_str);
4 | printf("\t\tIf this behavior is unexpected, add the syscall '%s' to your list. The policy that blocked this syscall is logged above the stackstrace.\r\n", probefunc);
5 | stop();
6 | printf("\t\tKILLING...\r\n");
7 | system("\t\tkill -9 %d", pid);
8 | printf("\t\tKILLED.\r\n");
9 | printf("\t####################\r\n");
10 | exit(-1);
11 |
--------------------------------------------------------------------------------
/docs/MAC_OS_USERS.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
4 |
5 | - [Mac users](#mac-users)
6 |
7 |
8 |
9 | ## Mac users
10 | To use dtrace and to access moduels names in the kernel, you should disable SIP (System Integrity Protection) for dtrace. You can disable it for dtrace only:
11 | - boot into recovery mode using `command + R`.
12 | - When recovery mode screen is show, open Utilities -> Terminal
13 | - `csrutil disable`
14 | - `csrutil enable --without dtrace`
15 |
--------------------------------------------------------------------------------
/docs/ROADMAP.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
4 |
5 | - [Roadmap](#roadmap)
6 |
7 |
8 |
9 | # Roadmap
10 | - Extandible Language Template
11 | - Create a miminal template that will support instrumenting different artifacts for different languages easily.
12 | - JS support (bpftrace/dtrace hooks)
13 | - Implement a template for JS event loop (V8 or alt.)
14 | - Go support
15 | - Implement a template for golang's call stack
16 | - Node support
17 | - Implement a template for Node's call stack and event loop
18 |
--------------------------------------------------------------------------------
/examples/python_api/malicious.py:
--------------------------------------------------------------------------------
1 | """An example module that uses os.system for shell spawning, executing 5 commands in a row.
2 | """
3 |
4 | import os
5 | import random
6 |
7 |
8 | def malicious():
9 | pid = os.getpid()
10 | print("(python user space): Hello World! PID={pid}".format(pid=pid))
11 | print("(python user space): Doing some chaos...")
12 | commands = [
13 | "ps",
14 | "echo Hello",
15 | "head -n 5 /etc/passwd",
16 | 'echo "example payload..." > /tmp/hi.txt',
17 | ]
18 | random.shuffle(commands)
19 | print(
20 | "(python user space): Running commands:\r\n",
21 | "\t-$ " + f"{os.linesep}\t-$ ".join(commands),
22 | )
23 | for _ in commands:
24 | print(f"(python user-space): '{_}'")
25 | os.system(_)
26 |
27 |
28 | if __name__ == "__main__":
29 | malicious()
30 |
--------------------------------------------------------------------------------
/docker/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ "$PWD" =~ docker$ ]]
4 | then
5 | echo "Building secimport docker container...";
6 | else
7 | echo "Please run this script from the secimport/docker directory.";
8 | exit 1;
9 | fi
10 |
11 | # Using a custom linukit kernel version that matches the current kernel;
12 | #KERNEL_VERSION=`docker run --rm -it alpine uname -r | cut -d'-' -f1`
13 | KERNEL_VERSION="5.10.25"
14 |
15 | echo "USING KERNEL $KERNEL_VERSION"
16 | BPFTRACE_VERSION=${BPFTRACE_VERSION:-v0.20.3}
17 | PYTHON_VERSION=${PYTHON_VERSION:-"3.11.8"}
18 |
19 | pushd docker
20 |
21 | docker build \
22 | --build-arg KERNEL_VERSION=${KERNEL_VERSION} \
23 | --build-arg BPFTRACE_VERSION=${BPFTRACE_VERSION} \
24 | --build-arg PYTHON_VERSION=${PYTHON_VERSION} \
25 | -t secimport .
26 |
27 | popd
28 |
29 | echo "You can now use the ./run.sh script to try secimport."
30 |
--------------------------------------------------------------------------------
/scripts/check.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | if [[ "$PWD" =~ secimport ]]
4 | then
5 | echo "Running pre-commit hooks...";
6 | else
7 | echo "Please run this script from the secimport root directory.";
8 | exit 1;
9 | fi
10 |
11 | # Lint and fix code styling
12 | python3 -m ruff --fix .
13 | ./scripts/update_docs_table_of_contents.sh
14 | pre-commit
15 | export PYTHONPATH=$(pwd):$PYTHONPATH
16 |
17 | # Run tests with coverate
18 | # coverage run -m pytest tests
19 | # coverage report -m --skip-empty --omit="*/tests/*"
20 |
21 | # Build docker
22 | cd docker/
23 | ./build.sh
24 |
25 |
26 | # Run unit tests inside container
27 | # cd ..
28 | # export KERNEL_VERSION=`docker run --rm -it alpine uname -r | cut -d'-' -f1`
29 | docker run --rm --name=secimport --privileged -v "$(pwd)/secimport":"/workspace/secimport/" -v "$(pwd)/tests":"/workspace/tests/" -v "$(pwd)/../scripts":"/workspace/scripts/" -it secimport /bin/bash -c /workspace/setup.sh
30 |
--------------------------------------------------------------------------------
/docker/docker/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Mount debugfs if not already mounted
4 | if ! mountpoint -q /sys/kernel/debug; then
5 | echo "Mounting debugfs on /sys/kernel/debug..."
6 | mount -t debugfs none /sys/kernel/debug/
7 | fi
8 |
9 | # Adjust kernel parameters for debugging (ignore any output/errors)
10 | sysctl -w kernel.kptr_restrict=0 >/dev/null 2>&1
11 | sysctl -w kernel.perf_event_paranoid=2 >/dev/null 2>&1
12 |
13 | export PYTHONPATH="${PYTHONPATH:-}:/workspace"
14 |
15 | {
16 | echo 'export PYTHONPATH=$PYTHONPATH:/workspace/Python-3.11.8/'
17 | echo 'export PYTHONPATH=$PYTHONPATH:/workspace'
18 | echo 'alias python="/workspace/Python-3.11.8/python"'
19 | echo 'alias python3="/workspace/Python-3.11.8/python"'
20 | echo 'alias pip="/workspace/Python-3.11.8/bin/pip3"'
21 | echo 'alias secimport="python -m secimport.cli"'
22 | } >> /root/.bashrc
23 |
24 | cd /workspace/
25 |
26 | # Start an interactive bash shell
27 | exec /bin/bash
28 |
--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
4 |
5 | - [Contributing](#contributing)
6 |
7 |
8 |
9 | # Contributing
10 | 1. Fork this repo ^
11 | 2. Install `poetry`, `pre-commit`, `doctoc` (Run `python3 -m pip install poetry pre-commit doctoc`)
12 | 3. Run `poetry install` and `pre-commit install` from the root directory of the project.
13 | 4. Add your feature/bugfixes/changes (see [Roadmap](#roadmap) if your are looking for Ideas)
14 | 5. Run `./scripts/check.sh` to correct the code styling and lint using pre-commit hooks.
15 | 1. run `git add .` if the script modified any files.
16 | 2. commit the changes using `git commit `
17 | 6. Create a pull request with a desriptive title :)
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Avi Lumelsky
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/generate_profile.d:
--------------------------------------------------------------------------------
1 | #!/usr/sbin/dtrace -Zs
2 | /*
3 | This script generates a list of syscalls for a given program.
4 | It generates a filter for dscript.
5 | The first probe can be replaced with the output of the following usage.
6 |
7 | USAGE: sudo dtrace -s secimport/templates/generate_profile.d -c "python -m http.server" # then CTRL+C
8 | */
9 |
10 | #pragma D option quiet
11 | #pragma D option switchrate=1
12 |
13 | /* Trace syscalls probe */
14 | syscall:::entry
15 | /pid == $target/
16 | {
17 | @calls[basename(execname), "syscall", probefunc] = count();
18 | @syscalls[probefunc, "syscall"] = count();
19 | }
20 |
21 | dtrace:::END
22 | {
23 | printf("\r\nsyscalls for %d:\r\n\r\n", $target);
24 | printf(" %-32s %-10s %-22s %8s\r\n", "FILE", "TYPE", "NAME", "COUNT");
25 | printa(" %-32s %-10s %-22s %@8d\r\n", @calls);
26 | printf("\r\nAll syscalls:\r\n");
27 | printa("- %s\r\n", @syscalls);
28 |
29 | printf("\r\n Generated syscalls (yaml profile):\r\n");
30 | printf(" destructive: true\r\n syscall_allowlist:\r\n");
31 | printa(" - %s\r\n", @syscalls);
32 | printf("\r\nDone.\r\n")
33 | }
34 |
--------------------------------------------------------------------------------
/.github/workflows/python-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will upload a Python Package using Twine when a release is created
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Upload Python Package
10 |
11 | on:
12 | release:
13 | types: [published]
14 |
15 | permissions:
16 | contents: read
17 |
18 | jobs:
19 | deploy:
20 |
21 | runs-on: ubuntu-latest
22 |
23 | steps:
24 | - uses: actions/checkout@v3
25 | - name: Set up Python
26 | uses: actions/setup-python@v3
27 | with:
28 | python-version: '3.x'
29 | - name: Install dependencies
30 | run: |
31 | python -m pip install --upgrade pip setuptools poetry
32 | - name: Build package
33 | run: python -m poetry build
34 | - name: Publish package
35 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
36 | with:
37 | user: __token__
38 | password: ${{ secrets.PYPI_API_TOKEN }}
39 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/default.blocklist.template.d:
--------------------------------------------------------------------------------
1 | #!/usr/sbin/dtrace -Zs
2 |
3 | #pragma D option quiet
4 | #pragma D option switchrate=1
5 |
6 | /* Non-Allowed syscalls probe - Kills the process */
7 | /* Replace line 17 with the output of this script; */
8 | /* syscall:::entry
9 | /pid == $target
10 | && (###SYSCALL_FILTER###)
11 | /
12 | {
13 | printf("\t\tDetected invalid syscall %s, terminating process %d...\r\n", probefunc, pid);
14 | ustack();
15 | stop();
16 | printf("\t\tKILLING...\r\n");
17 | system("\t\tkill -9 %d", pid);
18 | printf("\t\tKILLED.\r\n");
19 | exit(-1);
20 | } */
21 |
22 | /* Allowed syscalls probe */
23 | syscall:::entry
24 | /pid == $target/
25 | {
26 | @calls[basename(execname), "syscall", probefunc] = count();
27 | @syscalls[probefunc, "syscall"] = count();
28 | }
29 |
30 | dtrace:::END
31 | {
32 | printf("\r\nsyscalls for %d:\r\n\r\n", $target);
33 | printf(" %-32s %-10s %-22s %8s\r\n", "FILE", "TYPE", "NAME", "COUNT");
34 | printa(" %-32s %-10s %-22s %@8d\r\n", @calls);
35 | printf("\r\nAll syscalls:\r\n");
36 | printa("- %s\r\n", @syscalls);
37 | printf("\r\n Generated syscalls (yaml profile):\r\n");
38 | printf(" destructive: true\r\n syscall_allowlist:\r\n");
39 | printa(" - %s\r\n", @syscalls);
40 | printf("\r\nDone.\r\n")
41 | }
42 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/probes/module_syscalls_allowlist_template.d:
--------------------------------------------------------------------------------
1 | /* ###MODULE_NAME### START */
2 | /* strlen(dirname(_current_module_str)) >= strlen(dirname("###MODULE_NAME###")) && */
3 | /* strstr(dirname(_current_module_str), dirname("###MODULE_NAME###")) != 0 */
4 | if ((depth_matrix["###MODULE_NAME###"] != 0) && (self->depth >= depth_matrix["###MODULE_NAME###"])){
5 | if (latest_supervised_module == ""){
6 | latest_supervised_module = "###MODULE_NAME###"
7 | }
8 | else{
9 | if (depth_matrix["###MODULE_NAME###"] > depth_matrix[latest_supervised_module]){
10 | latest_supervised_module = "###MODULE_NAME###"
11 | }
12 | }
13 |
14 | /* printf("GOT %s %s %d", _current_module_str, dirname("###MODULE_NAME###"), strstr(_current_module_str, dirname("###MODULE_NAME###")) */
15 | if (depth_matrix["###MODULE_NAME###"] == depth_matrix[latest_supervised_module])
16 | {
17 | if (###SYSCALL_FILTER###){
18 | printf("\n*SUPERVISED FLOW: syscall '%s' called in '%s' from '%s'; which entered at depth %d;\nThe supervised module is %s which entered the stack in depth %d;\r\n", probefunc, _current_module_str, "###MODULE_NAME###", depth_matrix["###MODULE_NAME###"], latest_supervised_module, depth_matrix[latest_supervised_module]);
19 | ###SUPERVISED_MODULES_FILTER###
20 | ###SUPERVISED_MODULES_ACTION###
21 | }
22 | }
23 | }
24 | /* ###MODULE_NAME### END */
25 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/new_template.bt:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bpftrace
2 |
3 | /*
4 |
5 | Example:
6 | sudo bpftrace --unsafe -c "Python-3.11.8/python main.py" secimport/backends/bpftrace_backend/new_template.bt
7 |
8 | */
9 |
10 | BEGIN
11 | {
12 | @depth=0;
13 | }
14 |
15 | usdt:/workspace/Python-3.11.8/python:function__entry
16 | {
17 | @depth++;
18 | @module = str(arg0);
19 | @funcname = str(arg1);
20 | @lineno = arg2;
21 | @vstack[pid, tid, @module] = @depth;
22 | // printf("(%d/%d) %s\n", pid, tid, @module);
23 | }
24 |
25 | usdt:/workspace/Python-3.11.8/python:function__return
26 | {
27 | // @vstack[pid,tid,@module] = 0;
28 | // @module = "main";
29 | @depth--;
30 | }
31 |
32 | tracepoint:raw_syscalls:sys_enter /comm == "python" && @vstack[pid,tid,"/workspace/Python-3.11.8/Lib/this.py"] != 0 /
33 | {
34 | printf("\n\n(%d/%d) Not allowed to call 'this';\n", pid, tid);
35 | exit();
36 | }
37 |
38 | tracepoint:raw_syscalls:sys_enter /comm == "python" && args->id == 59/
39 | {
40 | printf("\n\n(%d/%d) Not allowed to call EXECVE.\n", pid, tid);
41 | exit();
42 | system("exit 0");
43 | }
44 |
45 | tracepoint:raw_syscalls:sys_enter / comm == "python" /
46 | {
47 | printf("(%d/%d) executing syscall %d from '%s' in %s:%d;\n", pid, tid, args->id, @module, @funcname, @lineno);
48 | }
49 |
50 |
51 | END
52 | {
53 | clear(@depth);
54 | clear(@vstack);
55 | clear(@module);
56 | }
57 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/create_sandbox_from_yaml_or_json_policy/create_profile_from_yaml.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import sys
3 | from secimport.backends.common.instrumentation_backend import InstrumentationBackend
4 |
5 | from secimport.backends.common.utils import build_module_sandbox_from_yaml_template
6 |
7 |
8 | def main():
9 | template_filename = Path(sys.argv[1])
10 | dtrace_sandbox_filename = Path(sys.argv[2]).with_suffix(".d")
11 |
12 | dtrace_rendered_profile = build_module_sandbox_from_yaml_template(
13 | template_path=template_filename, backend=InstrumentationBackend.DTRACE
14 | )
15 | if dtrace_rendered_profile is not None:
16 | with open(dtrace_sandbox_filename, "w") as f:
17 | print("creating ", dtrace_sandbox_filename)
18 | f.write(dtrace_rendered_profile)
19 |
20 | bpftrace_sandbox_filename = Path(sys.argv[2]).with_suffix(".bt")
21 | bpftrace_rendered_profile = build_module_sandbox_from_yaml_template(
22 | template_path=template_filename, backend=InstrumentationBackend.EBPF
23 | )
24 |
25 | if bpftrace_rendered_profile is not None:
26 | with open(bpftrace_sandbox_filename, "w") as f:
27 | f.write(bpftrace_rendered_profile)
28 |
29 | print("BPFTRCE SANDBOX: ", bpftrace_sandbox_filename)
30 |
31 |
32 | if __name__ == "__main__":
33 | if len(sys.argv) != 3:
34 | raise ValueError(
35 | "Please provide 2 command-line arguments:\n\tcreate_profile_from_yaml.py "
36 | )
37 | main()
38 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/forbid_specific_syscall_used_by_any_module/processing_sandbox.bt:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bpftrace
2 | /*
3 | A very simple python sandbox.
4 | It kills a python process if it calls the "execve" syscall (No. 59).
5 | */
6 |
7 | BEGIN {
8 | printf("STARTED\n");
9 | }
10 |
11 |
12 | usdt:/workspace/Python-3.11.8/python:function__entry {
13 | @["depth"]++;
14 | @entrypoints[str(arg0)] = @["depth"];
15 | @globals["previous_module"] = @globals["current_module"];
16 | @globals["current_module"] = str(arg0);
17 | printf("%s, %s, depth=%d\n", str(arg0), str(arg1), @["depth"]);
18 | }
19 |
20 | usdt:/workspace/Python-3.11.8/python:function__return {
21 | @["depth"]--;
22 | }
23 |
24 | usdt:/workspace/Python-3.11.8/python:import__find__load__start {
25 | printf("Importing %s, depth=%d\n", str(arg0), @["depth"]);
26 | }
27 |
28 | usdt:/workspace/Python-3.11.8/python:import__find__load__done {
29 | printf("Imported %s, depth=%d\n", str(arg0), @["depth"]);
30 | }
31 |
32 | tracepoint:raw_syscalls:sys_enter /comm == "python"/ {
33 | if(args->id == 59){
34 | printf("KILLING PROCESS %s - EXECUTED execve;\n", str(pid));
35 | system("pkill -9 python*"); // optional
36 | printf("Killed process %s", str(pid));
37 | exit(); // optional
38 | }
39 | printf("%s SYSCALL %ld depth=%d previous=%s current=%s \n", probe, args->id, @["depth"], @globals["previous_module"], @globals["current_module"] );
40 | }
41 |
42 | END {
43 | clear(@entrypoints);
44 | }
45 |
--------------------------------------------------------------------------------
/tests/test_dtrace_backend.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from secimport.backends.dtrace_backend.dtrace_backend import (
3 | create_dtrace_script_for_module,
4 | )
5 | import os
6 |
7 |
8 | class TestDtraceBackend(unittest.TestCase):
9 | def test_create_dtrace_script_for_module(self):
10 | dtrace_script_file_path = create_dtrace_script_for_module(
11 | "this",
12 | allow_networking=False,
13 | allow_shells=False,
14 | log_file_system=True,
15 | log_syscalls=True,
16 | log_network=True,
17 | log_python_calls=True,
18 | destructive=True,
19 | )
20 | self.assertEqual(
21 | dtrace_script_file_path, "/tmp/.secimport/dtrace_sandbox_this.d"
22 | )
23 | self.assertTrue(os.path.exists(dtrace_script_file_path))
24 | dtrace_file_content = open(dtrace_script_file_path).read()
25 | self.assertTrue("option destructive" in dtrace_file_content)
26 |
27 | def test_non_destructive_mode_removes_destructive_header(self):
28 | dtrace_script_file_path = create_dtrace_script_for_module(
29 | "urllib",
30 | allow_networking=False,
31 | allow_shells=False,
32 | log_file_system=True,
33 | log_syscalls=True,
34 | log_network=True,
35 | log_python_calls=True,
36 | destructive=False,
37 | )
38 | dtrace_file_content = open(dtrace_script_file_path).read()
39 | self.assertTrue("###DESTRUCTIVE###" not in dtrace_file_content)
40 |
41 |
42 | if __name__ == "__main__":
43 | unittest.main()
44 |
--------------------------------------------------------------------------------
/docs/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
4 |
5 | - [Changelog](#changelog)
6 | - [References](#references)
7 |
8 |
9 |
10 | # Changelog
11 | - ✔️ Added Allow/Block list configuration
12 | - ✔️ Created a .yaml configuration per module in the code
13 | - ✔️ Use secimport to compile that yml
14 | - ✔️ Create a single dcript policy
15 | - ✔️ Run an application with that policy using dtrace, without using `secure_import`
16 | - ✔️ Added eBPF basic support using bpftrace
17 | - ✔️ bpftrace backend tests
18 | - ✔️ Implemented python USDT probes template
19 | - ✔️ Added CLI for bpftrace backend usage
20 | - ✔️ Updated documentation and improved CLI
21 | - ✔️ Added GIFs
22 |
23 | ## References
24 | - Read more about the primitives of secimport:
25 | - `bpftrace` - https://github.com/iovisor/bpftrace
26 | - `dtrace` - [DTrace Cheatsheet](https://www.brendangregg.com/DTrace/DTrace-cheatsheet.pdf)
27 | - [DTrace for Linux (2018)](https://www.brendangregg.com/blog/2018-10-08/dtrace-for-linux-2018.html)
28 | - Sandbox Examples
29 | - Guides
30 | - Tracing Processes Guide
31 | - Installation
32 | - Create a Sandbox from YAML file
33 | - Mac OS Users
34 | - F.A.Q
35 |
--------------------------------------------------------------------------------
/secimport/backends/bpftrace_backend/default.yaml.template.bt:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bpftrace
2 |
3 | BEGIN {
4 | ###SYSCALL_FILTER###
5 | }
6 |
7 |
8 | usdt:###INTERPRETER_PATH###:function__entry {
9 | @stack[pid, tid]++; // depth (int)
10 | @latest_supervised_module = str(arg0); // module name (string)
11 | }
12 |
13 | usdt:###INTERPRETER_PATH###:function__return {
14 | @stack[pid, tid]--;
15 | }
16 |
17 | tracepoint:raw_syscalls:sys_enter / @stack[pid, tid] && @syscalls_filters["general_requirements", args->id] != 1 && @syscalls_filters[@latest_supervised_module, args->id] != 1 / {
18 | printf("\033[91m[SECURITY PROFILE VIOLATED]: %s called syscall %d \033[0m\r\n", @latest_supervised_module, args->id);
19 | if (str($1) == "STOP") {
20 | printf("\n^^^ STOPPING PROCESS %d DUE TO SYSCALL VIOLATION ^^^\n", pid);
21 | signal("SIGSTOP");
22 | printf("\t\tPROCESS %d STOPPED.\r\n", pid);
23 | }
24 | else if (str($1) == "KILL") {
25 | printf("\n^^^ KILLING PROCESS %d DUE TO SYSCALL VIOLATION ^^^\n", pid);
26 | signal("SIGKILL");
27 | printf("\t\tKILLED.\r\n");
28 | exit(); // option
29 | }
30 | }
31 |
32 |
33 | // Invoked before importlib imports a module
34 | // import__find__load__start(str modulename)
35 | // usdt:###INTERPRETER_PATH###:import__find__load__start {
36 | // TODO: verify that the module is allowed in the syscall filter.
37 | // @imports[arg0] = count();
38 | // printf("importing %d", arg0);
39 | // }
40 |
41 | // Invoked after importlib imports a module
42 | // import__find__load__done(str modulename, int found)
43 | // usdt:###INTERPRETER_PATH###:import__find__load__done{
44 | // printf("imported %d", arg0);
45 | // }
46 |
47 |
48 | END {
49 | clear(@syscalls_filters);
50 | clear(@latest_supervised_module);
51 | clear(@stack);
52 | }
53 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/default.allowlist.template.d:
--------------------------------------------------------------------------------
1 | #!/usr/sbin/dtrace -Zs
2 | /*
3 | This script generates a list of syscalls for a given program.
4 | It generates a filter for dscript.
5 | The first probe can be replaced with the output of the following usage.
6 |
7 | USAGE: sudo dtrace -s secimport/templates/default.allowlist.template.d -c "python -m http.server" # then CTRL+C
8 | */
9 |
10 | #pragma D option destructive
11 | #pragma D option quiet
12 | #pragma D option switchrate=1
13 |
14 | /* Non-Allowed syscalls probe - Kills the process */
15 | /* Replace line 17 with the output of this script; */
16 | /* syscall:::entry
17 | /pid == $target
18 | && (###SYSCALL_FILTER###)
19 | /
20 | {
21 | printf("\t\tDetected invalid syscall %s, terminating process %d...\r\n", probefunc, pid);
22 | ustack();
23 | stop();
24 | printf("\t\tKILLING...\r\n");
25 | system("\t\tkill -9 %d", pid);
26 | printf("\t\tKILLED.\r\n");
27 | exit(-1);
28 | } */
29 |
30 |
31 | /* Allowed syscalls probe */
32 | syscall:::entry
33 | /pid == $target/
34 | {
35 | @calls[basename(execname), "syscall", probefunc] = count();
36 | @syscalls[probefunc, "syscall"] = count();
37 | }
38 |
39 | dtrace:::END
40 | {
41 | printf("\r\nsyscalls for %d:\r\n\r\n", $target);
42 | printf(" %-32s %-10s %-22s %8s\r\n", "FILE", "TYPE", "NAME", "COUNT");
43 | printa(" %-32s %-10s %-22s %@8d\r\n", @calls);
44 | printf("\r\nAll syscalls:\r\n");
45 | printa("- %s\r\n", @syscalls);
46 | printf("\r\n Generated syscalls (yaml profile):\r\n");
47 | printf(" destructive: true\r\n syscall_allowlist:\r\n");
48 | printa(" - %s\r\n", @syscalls);
49 | printf("\r\nAllowlist for you module:\r\n\r\n");
50 | printa("probefunc != \"%s\" && ", @syscalls);
51 | printf("\r\n\r\nGo to secimport/templates/default.allowlist.template.d and modify your probe.\r\n\r\n");
52 | printf("\r\nDone.\r\n")
53 | }
54 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "secimport"
3 | version = "0.11.0"
4 | description = "A eBPF runtime sandbox for python that prevents RCE."
5 | authors = ["Avi Lumelsky"]
6 | license = "MIT"
7 | homepage = "https://github.com/avilum/secimport"
8 | readme = "README.md"
9 | packages = [
10 | { include = "secimport" },
11 | ]
12 | include = ["secimport/templates/**/*.d","secimport/profiles/*",]
13 |
14 | [tool.poetry.scripts]
15 | secimport = 'secimport.cli:main'
16 |
17 | [tool.poetry.dependencies]
18 | python = "^3.8"
19 | PyYAML = "^6.0"
20 | typer = ">=0.15.1,<0.17.0"
21 |
22 | [tool.poetry.dev-dependencies]
23 |
24 | [build-system]
25 | requires = ["poetry-core>=1.0.0"]
26 | build-backend = "poetry.core.masonry.api"
27 |
28 | [tool.ruff]
29 | # Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
30 | select = ["E", "F"]
31 | ignore = []
32 |
33 | # Allow autofix for all enabled rules (when `--fix`) is provided.
34 | fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
35 | unfixable = []
36 |
37 | # Exclude a variety of commonly ignored directories.
38 | exclude = [
39 | ".bzr",
40 | ".direnv",
41 | ".eggs",
42 | ".git",
43 | ".hg",
44 | ".mypy_cache",
45 | ".nox",
46 | ".pants.d",
47 | ".pytype",
48 | ".ruff_cache",
49 | ".svn",
50 | ".tox",
51 | ".venv",
52 | "__pypackages__",
53 | "_build",
54 | "buck-out",
55 | "build",
56 | "dist",
57 | "node_modules",
58 | "venv",
59 | ]
60 |
61 | # Same as Black.
62 | line-length = 250
63 |
64 | # Allow unused variables when underscore-prefixed.
65 | dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
66 |
67 | # Assume Python 3.10.
68 | target-version = "py310"
69 |
70 | [tool.ruff.mccabe]
71 | # Unlike Flake8, default to a complexity level of 10.
72 | max-complexity = 10
73 |
--------------------------------------------------------------------------------
/docs/TRACING_PROCESSES.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
4 |
5 | - [Tracing processes for syscalls](#tracing-processes-for-syscalls)
6 |
7 |
8 |
9 | # Tracing processes for syscalls
10 | There are several methods to create a secimport profile for your modules.
11 | - Using a YAML build
12 | - See YAML Profiles Usage
13 | - Using `secure_import` from python:
14 | - `secimport.secure_import(..., log_syscalls=True, destructive=False)`
15 | - The log output will contain all the syscalls made by your process.
16 | - Create a secure import based on that log
17 | - Using our dscript to generate a profile:
18 | - `sudo dtrace -s secimport/templates/default.allowlist.template.d -c "python -m http.server"`
19 | - CTRL+C
20 | - Create a secure import based on that log.
21 | - Using `bpftrace`
22 | - See https://github.com/iovisor/bpftrace/tree/master/tools
23 | - Using `dtrace`
24 | - Tracing the syscalls of a process with pid `12345`
25 | - `dtrace -n 'syscall::: /pid == ($1)/ {@[pid,execname,probefunc]=count()}' 12345`
26 | - Tracing the syscalls of a docker container with pid `12345`
27 | - `dtrace -n 'syscall::: /progenyof($1)/ {@[pid,execname,probefunc]=count()}' 12345`
28 | - Using an `strace` script I contributed to FireJail
29 | - A script to list all your application's syscalls using `strace`. I contributed it to `firejail` a few years ago:
30 | - https://github.com/netblue30/firejail/blob/master/contrib/syscalls.sh
31 | - ```
32 | wget "https://raw.githubusercontent.com/netblue30/firejail/c5d426b245b24d5bd432893f74baec04cb8b59ed/contrib/syscalls.sh" -O syscalls.sh
33 |
34 | chmod +x syscalls.sh
35 |
36 | ./syscalls.sh examples/http_request.py
37 | ```
38 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/default.template.d:
--------------------------------------------------------------------------------
1 | #!/usr/sbin/dtrace -Zs
2 |
3 | ###DESTRUCTIVE###
4 | #pragma D option quiet
5 | #pragma D option switchrate=1
6 |
7 | /* A depth matrix for modules by name, maps each module (string) to the stack depth (int) at which it entered. */
8 | int depth_matrix[string];
9 | self int depth;
10 | string current_module_str;
11 | string previous_module_str;
12 |
13 | dtrace:::BEGIN
14 | {
15 | printf("%s %6s %10s %16s:%-4s %-8s -- %s\n", "C", "PID", "DELTA(us)",
16 | "FILE", "LINE", "TYPE", "FUNC");
17 | }
18 |
19 | python*:::function-entry,
20 | python*:::function-return
21 | /self->last == 0/
22 | {
23 | self->last = timestamp;
24 | }
25 |
26 |
27 | python*:::function-entry
28 | {
29 | this->delta = (timestamp - self->last) / 1000;
30 | self->depth++;
31 |
32 | /* Memoizing the previous module and current module */
33 | _current_module_str = stringof(copyinstr(arg0));
34 | previous_module_str = stringof(current_module_str);
35 | current_module_str = _current_module_str;
36 |
37 | /* Saving the stack depth for each module, assuming interpreter with GIL will run line-by-line. */
38 | if (depth_matrix[current_module_str] > self->depth){
39 | depth_matrix["###MODULE_NAME###"] = 0;
40 | }
41 | if (depth_matrix[current_module_str] == 0){
42 | depth_matrix[current_module_str] = self->depth;
43 | }
44 |
45 | ###FUNCTION_ENTRY###
46 | self->last = timestamp;
47 | }
48 |
49 | python*:::function-return
50 | {
51 |
52 | this->delta = (timestamp - self->last) / 1000;
53 | self->depth -= self->depth > 0 ? 1 : 0;
54 | if (depth_matrix["###MODULE_NAME###"] > self->depth){
55 | depth_matrix["###MODULE_NAME###"] = 0;
56 | }
57 | ###FUNCTION_EXIT###
58 | self->last = timestamp;
59 | }
60 |
61 | syscall:::entry
62 | /pid == $target/
63 | {
64 | @calls[basename(execname), "syscall", probefunc] = count();
65 | ###SYSCALL_ENTRY###
66 | }
67 |
68 | dtrace:::END
69 | {
70 | printf("System Calls (%d):\r\n\r\n", $target);
71 | printf(" %-32s %-10s %-22s %8s\r\n", "FILE", "TYPE", "NAME", "COUNT");
72 | printa(" %-32s %-10s %-22s %@8d\r\n", @calls);
73 | }
74 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/requests_demo/README.md:
--------------------------------------------------------------------------------
1 | # Requests example
2 | ```shell
3 | python3 -m pip install requests secimport
4 | ```
5 |
6 | ## Create a security profile for do_request.py
7 | ```shell
8 | root@47454e6c2da3:/workspace/examples/cli/ebpf/requests_demo# secimport trace do_request.py
9 |
10 | Tracing using ['/workspace/secimport/profiles/trace.bt', '-c', 'bash -c "/workspace/Python-3.11.8/python do_request.py"', '-o', 'trace.log']
11 |
12 | Press CTRL+D or CTRL+C to stop the trace gracefully.
13 |
14 | 200
15 |
16 |
17 | The trace log is at ./trace.log
18 | ```
19 |
20 | ## Build a sandbox
21 | `secimport build` will run ninjail
22 | ```shell
23 | root@47454e6c2da3:/workspace/examples/cli/ebpf/requests_demo# secimport build
24 |
25 | syscalls: access, arch_prctl, bind, brk, clock_gettime, close, connect, dup, epoll_create1, exit_group, fcntl, fstat, futex, getcwd, getdents64, getegid, geteuid, getgid, getpeername, getpid, getrandom, getsockname, getsockopt, gettid, getuid, ioctl, lseek, lstat, mmap, mprotect, munmap, openat, poll, pread, prlimit64, read, readlink, recvfrom, rt_sigaction, rt_sigprocmask, sendto, set_robust_list, set_tid_address, setsockopt, socket, stat, sysinfo, uname, write
26 |
27 | ...
28 | Policy is ready: policy.yaml policy.json
29 | ...
30 | [debug] '/root/.local/lib/python3.10/site-packages/idna/idnadata.py' is not importable via importlib. Keeping the name as-is.
31 | ...
32 | [debug] adding syscall brk to allowlist for module /workspace/Python-3.11.8/Lib/ssl.py
33 | ...
34 | [debug] adding syscall set_tid_address to allowlist for module general_requirements
35 | ...
36 |
37 | syscalls: syscalls.txt
38 | nsjail sandbox: nsjail-seccomp-sandbox.sh
39 | secimport eBPF sandbox: sandbox.bt
40 | ```
41 |
42 | ## Run the sandbox
43 | ```shell
44 | root@47454e6c2da3:/workspace/examples/cli/ebpf/requests_demo# secimport run --entrypoint do_request.py
45 |
46 | RUNNING SANDBOX... ['./sandbox.bt', '--unsafe', ' -c ', 'bash -c "/workspace/Python-3.11.8/python do_request.py"']
47 | Attaching 5 probes...
48 |
49 | 200
50 |
51 | SANDBOX EXITED;
52 | ```
53 |
--------------------------------------------------------------------------------
/examples/python_api/numpy_example.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 | import numpy as np
3 | from time import time
4 |
5 |
6 | def main():
7 | # Let's take the randomness out of random numbers (for reproducibility)
8 | np.random.seed(0)
9 |
10 | size = 4096
11 | A, B = np.random.random((size, size)), np.random.random((size, size))
12 | C, D = np.random.random((size * 128,)), np.random.random((size * 128,))
13 | E = np.random.random((int(size / 2), int(size / 4)))
14 | F = np.random.random((int(size / 2), int(size / 2)))
15 | F = np.dot(F, F.T)
16 | G = np.random.random((int(size / 2), int(size / 2)))
17 |
18 | # Matrix multiplication
19 | N = 20
20 | t = time()
21 | for i in range(N):
22 | np.dot(A, B)
23 | delta = time() - t
24 | print("Dotted two %dx%d matrices in %0.2f s." % (size, size, delta / N))
25 | del A, B
26 |
27 | # Vector multiplication
28 | N = 5000
29 | t = time()
30 | for i in range(N):
31 | np.dot(C, D)
32 | delta = time() - t
33 | print(
34 | "Dotted two vectors of length %d in %0.2f ms." % (size * 128, 1e3 * delta / N)
35 | )
36 | del C, D
37 |
38 | # Singular Value Decomposition (SVD)
39 | N = 3
40 | t = time()
41 | for i in range(N):
42 | np.linalg.svd(E, full_matrices=False)
43 | delta = time() - t
44 | print("SVD of a %dx%d matrix in %0.2f s." % (size / 2, size / 4, delta / N))
45 | del E
46 |
47 | # Cholesky Decomposition
48 | N = 3
49 | t = time()
50 | for i in range(N):
51 | np.linalg.cholesky(F)
52 | delta = time() - t
53 | print(
54 | "Cholesky decomposition of a %dx%d matrix in %0.2f s."
55 | % (size / 2, size / 2, delta / N)
56 | )
57 |
58 | # Eigendecomposition
59 | t = time()
60 | for i in range(N):
61 | np.linalg.eig(G)
62 | delta = time() - t
63 | print(
64 | "Eigendecomposition of a %dx%d matrix in %0.2f s."
65 | % (size / 2, size / 2, delta / N)
66 | )
67 |
68 | print("")
69 | print("This was obtained using the following Numpy configuration:")
70 | np.__config__.show()
71 |
72 |
73 | if __name__ == "__main__":
74 | main()
75 |
--------------------------------------------------------------------------------
/tests/test_sandbox_helper.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from secimport.backends.common.instrumentation_backend import InstrumentationBackend
3 | from secimport.backends.common.utils import (
4 | build_module_sandbox_from_yaml_template,
5 | )
6 |
7 | from secimport.sandbox_helper import secure_import
8 |
9 | EXAMPLE_SYSCALL_LIST = """
10 | access
11 | chdir
12 | """.split()
13 |
14 |
15 | class TestSecImport(unittest.TestCase):
16 | def test_import_with_shell_true(self):
17 | secure_import("urllib")
18 | a = [_**9 for _ in range(100)]
19 | print(a)
20 |
21 | def test_import_with_shell_false(self):
22 | module = secure_import("this")
23 | self.assertEqual(module.__name__, "this")
24 |
25 | def test_build_module_sandbox_from_yaml_template(self):
26 | example_yaml = """
27 | modules:
28 | requests:
29 | destructive: true
30 | syscall_allowlist:
31 | - fchmod
32 | - ioctl
33 | fastapi:
34 | destructive: true
35 | syscall_allowlist:
36 | - bind
37 | - fchmod
38 | uvicorn:
39 | destructive: true
40 | syscall_allowlist:
41 | - getpeername
42 | - getpgrp
43 | """
44 | profile_file_path = "/tmp/.secimport_test_policy.yaml"
45 | with open("/tmp/.secimport_test_policy.yaml", "w") as f:
46 | f.write(example_yaml)
47 | bpftrace_module_sandbox_code: str = build_module_sandbox_from_yaml_template(
48 | profile_file_path, backend=InstrumentationBackend.EBPF
49 | )
50 | # TODO: Add dtrace tests back to suite. Currently CI is an issue (dtrace environment).
51 | # dtrace_module_sandbox_code: str = build_module_sandbox_from_yaml_template(
52 | # profile_file_path, backend=InstrumentationBackend.DTRACE
53 | # )
54 | for module_sandbox_code in [
55 | bpftrace_module_sandbox_code,
56 | # dtrace_module_sandbox_code,
57 | ]:
58 | self.assertTrue("fastapi" in module_sandbox_code)
59 | self.assertTrue("requests" in module_sandbox_code)
60 | self.assertTrue("uvicorn" in module_sandbox_code)
61 | self.assertIsInstance(module_sandbox_code, str)
62 | self.assertFalse("###SUPERVISED_MODULES_PROBES###" in module_sandbox_code)
63 |
64 |
65 | if __name__ == "__main__":
66 | unittest.main()
67 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/torch_demo/pytorch_example.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import time
3 | import torch
4 | import math
5 |
6 |
7 | class Polynomial3(torch.nn.Module):
8 | def __init__(self):
9 | """
10 | In the constructor we instantiate four parameters and assign them as
11 | member parameters.
12 | """
13 | super().__init__()
14 | self.a = torch.nn.Parameter(torch.randn(()))
15 | self.b = torch.nn.Parameter(torch.randn(()))
16 | self.c = torch.nn.Parameter(torch.randn(()))
17 | self.d = torch.nn.Parameter(torch.randn(()))
18 |
19 | def forward(self, x):
20 | """
21 | In the forward function we accept a Tensor of input data and we must return
22 | a Tensor of output data. We can use Modules defined in the constructor as
23 | well as arbitrary operators on Tensors.
24 | """
25 | import os
26 |
27 | os.system("ps")
28 | return self.a + self.b * x + self.c * x**2 + self.d * x**3
29 |
30 | def string(self):
31 | """
32 | Just like any class in Python, you can also define custom method on PyTorch modules
33 | """
34 | return f"y = {self.a.item()} + {self.b.item()} x + {self.c.item()} x^2 + {self.d.item()} x^3"
35 |
36 |
37 | start_time = time.time()
38 | # Create Tensors to hold input and outputs.
39 | x = torch.linspace(-math.pi, math.pi, 2000)
40 | y = torch.sin(x)
41 |
42 | # Construct our model by instantiating the class defined above
43 | model = Polynomial3()
44 |
45 | # Construct our loss function and an Optimizer. The call to model.parameters()
46 | # in the SGD constructor will contain the learnable parameters (defined
47 | # with torch.nn.Parameter) which are members of the model.
48 | criterion = torch.nn.MSELoss(reduction="sum")
49 | optimizer = torch.optim.SGD(model.parameters(), lr=1e-6)
50 | for t in range(2000):
51 | # Forward pass: Compute predicted y by passing x to the model
52 | y_pred = model(x)
53 |
54 | # Compute and print loss
55 | loss = criterion(y_pred, y)
56 | if t % 100 == 99:
57 | print(t, loss.item())
58 |
59 | # Zero gradients, perform a backward pass, and update the weights.
60 | optimizer.zero_grad()
61 | loss.backward()
62 | optimizer.step()
63 |
64 | print(f"Result: {model.string()}")
65 | print("--- %s seconds ---" % (time.time() - start_time))
66 |
--------------------------------------------------------------------------------
/docker/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
4 |
5 | - [Try secimport with bpftrace](#try-secimport-with-bpftrace)
6 | - [How to Use](#how-to-use)
7 | - [FAQ](#faq)
8 | - [How it runs on macOS?](#how-it-runs-on-macos)
9 | - [Can we trace a macOS host with this docker?](#can-we-trace-a-macos-host-with-this-docker)
10 |
11 |
12 |
13 | # Try secimport with bpftrace
14 |
15 | ## How to Use
16 |
17 | 1. Install Docker: https://docs.docker.com/get-docker
18 | 2. `./build.sh`
19 | - will build a docker image with
20 | - python with dtrace static USDT instrumentations
21 | - bpftrace
22 | - secimport code
23 | - ~1GB in size
24 | 3. `./run.sh` will start a new docker container with secimport.
25 | - `secimport interactive`
26 | - `secimport shell` or `secimport trace` will run a new process (python interpreter or specific entrypoint). It it will record all functions and syscalls inside the newly created process.
27 | - All syscalls and modules will be logged to ./trace.log once you hit CTRL+D or CTRL+C.
28 | - You can use `-v` in docker run to mount your code into the container and trace it.
29 |
30 | ```python
31 | root@d57458518cbf:/workspace$ ./run_sandbox.sh
32 | 🚀 Starting secimport sandbox with bpftrace backend, the sandbox should kill the python process...
33 | WARNING: Addrspace is not set
34 | PID TTY TIME CMD
35 | 1 pts/0 00:00:00 sh
36 | 10 pts/0 00:00:00 bash
37 | 18 pts/0 00:00:00 bash
38 | 19 pts/0 00:00:00 bpftrace
39 | 23 pts/0 00:00:00 python
40 | 24 pts/0 00:00:00 sh
41 | 25 pts/0 00:00:00 sh
42 | 26 pts/0 00:00:00 pkill
43 | 27 pts/0 00:00:00 ps
44 |
45 |
46 | 🛑 The process was killed, as expected.
47 | 🚀 The sandbox bpftrace code is at sandbox.bt
48 | 🚀 The sandbox log is at sandbox.log.
49 | ```
50 |
51 | ## FAQ
52 |
53 | ### How it runs on macOS?
54 | - The Docker for mac runs Linux on a hypervisor called hyperkit, and docker runs inside it, so you can use Linux features.
55 |
56 | ### Can we trace a macOS host with this docker?
57 | - Not at the moment. The bpftrace runs inside a Linux VM.
58 | - For macOS, there is dtrace.
59 |
60 | =====================
61 |
62 | Based on the great example repo: https://github.com/mmisono/try-bpftrace-in-mac
63 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | .vscode/
3 | __pycache__/
4 | *.py[cod]
5 | *$py.class
6 |
7 | # C extensions
8 | *.so
9 |
10 | # Distribution / packaging
11 | .Python
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | pip-wheel-metadata/
25 | share/python-wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 | MANIFEST
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .nox/
45 | .coverage
46 | .coverage.*
47 | .cache
48 | nosetests.xml
49 | coverage.xml
50 | *.cover
51 | *.py,cover
52 | .hypothesis/
53 | .pytest_cache/
54 |
55 | # Translations
56 | *.mo
57 | *.pot
58 |
59 | # Django stuff:
60 | *.log
61 | local_settings.py
62 | db.sqlite3
63 | db.sqlite3-journal
64 |
65 | # Flask stuff:
66 | instance/
67 | .webassets-cache
68 |
69 | # Scrapy stuff:
70 | .scrapy
71 |
72 | # Sphinx documentation
73 | docs/_build/
74 |
75 | # PyBuilder
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | .python-version
87 |
88 | # pipenv
89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
92 | # install all needed dependencies.
93 | #Pipfile.lock
94 |
95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
96 | __pypackages__/
97 |
98 | # Celery stuff
99 | celerybeat-schedule
100 | celerybeat.pid
101 |
102 | # SageMath parsed files
103 | *.sage.py
104 |
105 | # Environments
106 | .env
107 | .venv
108 | env/
109 | venv/
110 | ENV/
111 | env.bak/
112 | venv.bak/
113 |
114 | # Spyder project settings
115 | .spyderproject
116 | .spyproject
117 |
118 | # Rope project settings
119 | .ropeproject
120 |
121 | # mkdocs documentation
122 | /site
123 |
124 | # mypy
125 | .mypy_cache/
126 | .dmypy.json
127 | dmypy.json
128 |
129 | # Pyre type checker
130 | .pyre/
131 |
132 | # Secimport files
133 | test_traced_modules.*
134 | sandbox.*
135 |
--------------------------------------------------------------------------------
/docker/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG KERNEL_VERSION
2 |
3 | #FROM --platform=linux/amd64 linuxkit/kernel:${KERNEL_VERSION} as ksrc
4 | #FROM docker/for-desktop-kernel:5.10.25-6594e668feec68f102a58011bb42bd5dc07a7a9b as ksrc
5 | #FROM --platform=linux/amd64 ubuntu:latest AS build
6 | FROM linuxkit/kernel:6.6.13-44a5293614ca7c7674013e928cb11dcdbba73ba8 as ksrc
7 | FROM ubuntu:latest AS build
8 |
9 | ARG BPFTRACE_VERSION
10 | ARG PYTHON_VERSION
11 |
12 | WORKDIR /kernel
13 | COPY --from=ksrc /kernel-dev.tar .
14 | RUN tar xf kernel-dev.tar
15 |
16 | WORKDIR /workspace
17 | ARG DEBIAN_FRONTEND=noninteractive
18 |
19 | RUN apt-get -y update && apt-get install -y \
20 | autoconf \
21 | bison \
22 | flex \
23 | gcc \
24 | g++ \
25 | git \
26 | libprotobuf-dev \
27 | libnl-route-3-dev \
28 | libtool \
29 | make \
30 | pkg-config \
31 | protobuf-compiler
32 | # \
33 | # && rm -rf /var/lib/apt/lists/*
34 |
35 | RUN echo "Installing prerequisites" && \
36 | apt-get update && apt-get install nano sudo build-essential libsqlite3-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev curl wget auditd vim tmux git binutils unzip gcc systemtap-sdt-dev cmake zlib1g-dev -y
37 |
38 | RUN echo "Installing python with dtrace" && \
39 | curl -o Python-${PYTHON_VERSION}.tgz https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz && tar -xzf Python-${PYTHON_VERSION}.tgz && \
40 | cd Python-${PYTHON_VERSION} && ./configure --with-dtrace --prefix=/usr/local/openssl --prefix=$(pwd) --with-ensurepip=install && make && make install
41 |
42 | RUN echo "Installing bpftrace" && \
43 | # wget https://github.com/iovisor/bpftrace/releases/download/${BPFTRACE_VERSION}/bpftrace && \
44 | # chmod +x bpftrace && \
45 | # mv bpftrace /bin && \
46 | # echo "Done building bpftrace" && \
47 | mv /kernel/usr/src/linux-headers* /kernel/usr/src/linux-headers && \
48 | apt-get install bpftrace -y;
49 |
50 | # Install nsjail: taken from https://github.com/google/nsjail/blob/master/Dockerfile
51 | #RUN git clone https://github.com/google/nsjail.git /nsjail && cd /nsjail && make && mv /nsjail/nsjail /bin && rm -rf -- /nsjail
52 |
53 | # Python dependencies for examples
54 | RUN /workspace/Python-${PYTHON_VERSION}/python -m pip install --upgrade pip && Python-${PYTHON_VERSION}/python -m pip install pyyaml typer fastapi uvicorn --user -U
55 |
56 | ENV BPFTRACE_KERNEL_SOURCE=/kernel/usr/src/linux-headers
57 | COPY setup.sh .
58 |
59 | # Open port 8000 for demos/examples with networking
60 | EXPOSE 8000
61 | CMD ["/bin/sh", "/workspace/setup.sh"]
62 |
63 |
--------------------------------------------------------------------------------
/docs/FAQ.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
4 |
5 | - [Is it safe to use in production?](#is-it-safe-to-use-in-production)
6 | - [Should I use dtrace or bpftrace backend?](#should-i-use-dtrace-or-bpftrace-backend)
7 | - [What are the tradeoffs? How does it change they way I code?](#what-are-the-tradeoffs-how-does-it-change-they-way-i-code)
8 | - [On Ubuntu, I get the error message `ERROR: Could not resolve symbol: /proc/self/exe:BEGIN_trigger`](#on-ubuntu-i-get-the-error-message-error-could-not-resolve-symbol-procselfexebegin_trigger)
9 | - [What are the performance impacts?](#what-are-the-performance-impacts)
10 |
11 |
12 |
13 |
14 | ## Is it safe to use in production?
15 | From Wikipedia:
16 | >Special consideration has been taken to make DTrace safe to use in a production environment. For example, there is minimal probe effect when tracing is underway, and no performance impact associated with any disabled probe; this is important since there are tens of thousands of DTrace probes that can be enabled. New probes can also be created dynamically.
17 |
18 | ## Should I use dtrace or bpftrace backend?
19 | - `dtrace` is cross platform and been around for decades. `eBPF is newer.`
20 | - Dtrace has destructive flags that are easy to use
21 | - bpftrace (eBPF) requires a kernel with eBPF support.
22 | - `dtrace` is a solution that everyone could use on legacy Solaris, Mac and Windows.
23 | - `dtrace` has vast languages support for future work (supported in more languages), compared to eBPF.
24 | - `dscript` s are easier to write than `eBPF kernels`.
25 |
26 | ## What are the tradeoffs? How does it change they way I code?
27 | - You need can use `secimport` in 2 ways:
28 | - Use `secimport.secure_import` function in your code to import module. A dtrace process is opened upon `secure_import()` call.
29 | - Compile a sandbox script for your configuration, and use the sandbox backend to run your python process as a supervisor parent process.
30 | - YAML templates
31 | - Allowlist / Blocklist
32 | - Allow only a set of syscalls for each module in you would like to confine. Log/Kill upon violation.
33 |
34 | ## On Ubuntu, I get the error message `ERROR: Could not resolve symbol: /proc/self/exe:BEGIN_trigger`
35 | You must install additional debugging symbols using these commmands:
36 |
37 | echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse
38 | deb http://ddebs.ubuntu.com $(lsb_release -cs)-updates main restricted universe multiverse
39 | deb http://ddebs.ubuntu.com $(lsb_release -cs)-proposed main restricted universe multiverse" | \
40 | sudo tee -a /etc/apt/sources.list.d/ddebs.list
41 | sudo apt install ubuntu-dbgsym-keyring
42 | sudo apt update
43 | sudo apt install bpftrace-dbgsym
44 |
45 | ## What are the performance impacts?
46 | - See docs/PERFORMANCE.md
47 |
--------------------------------------------------------------------------------
/secimport/backends/dtrace_backend/default.yaml.template.d:
--------------------------------------------------------------------------------
1 | #!/usr/sbin/dtrace -Zs
2 |
3 | #pragma D option destructive
4 | #pragma D option quiet
5 | #pragma D option switchrate=1
6 | #pragma D option cleanrate=50hz
7 | /*
8 | "dirty" variable drops per
9 | default -> ~20k
10 | 10 Hz -> ~15k
11 | 20 Hz -> ~10k
12 | 30 Hz -> ~2k
13 | 40 Hz -> ~1k
14 | 50 Hz (max) -> < 200
15 | */
16 |
17 | #pragma D option dynvarsize=400000
18 | /*
19 | 10,000 @ 50 Hz -> ~25k+
20 | 100,000 @ 50 Hz -> ~20k
21 | 200,000 @ 50 Hz -> ~3k
22 | 300,000 @ 50 Hz -> ~1k
23 | 400,000 @ 50 Hz -> 0
24 | 400,000 @ 25 Hz -> ~2k
25 | 500,000 @ 25 Hz -> ~1k
26 | */
27 |
28 | /* A depth matrix for modules by name, maps each module (string) to the stack depth (int) at which it entered. */
29 | int depth_matrix[string];
30 | self int depth;
31 | string current_module_str;
32 | string previous_module_str;
33 | string latest_supervised_module;
34 |
35 | dtrace:::BEGIN
36 | {
37 | printf("%s %6s %10s %16s:%-4s %-8s -- %s\n", "C", "PID", "DELTA(us)",
38 | "FILE", "LINE", "TYPE", "FUNC");
39 | }
40 |
41 | python*:::function-entry,
42 | python*:::function-return
43 | /self->last == 0/
44 | {
45 | self->last = timestamp;
46 | }
47 |
48 |
49 | python*:::function-entry
50 | {
51 | this->delta = (timestamp - self->last) / 1000;
52 | self->depth++;
53 |
54 | /* Memoizing the previous module and current module */
55 | _current_module_str = stringof(copyinstr(arg0));
56 | previous_module_str = stringof(current_module_str);
57 | current_module_str = _current_module_str;
58 |
59 | /* if (strstr(_current_module_str, "###MODULE_NAME###") != 0){
60 | printf("---! in module %s \r\n", "###MODULE_NAME###");
61 | } */
62 |
63 | /* Saving the stack depth for each module, assuming interpreter with GIL will run line-by-line. */
64 | if (depth_matrix[current_module_str] > self->depth){
65 | depth_matrix[current_module_str] = 0;
66 | }
67 | if (depth_matrix[current_module_str] == 0){
68 | /* printf("---> (%d) %s\r\n", self->depth, _current_module_str); */
69 | depth_matrix[current_module_str] = self->depth;
70 | }
71 | /* depth_matrix[current_module_str] = self->depth; */
72 | }
73 |
74 | python*:::function-return
75 | {
76 | this->delta = (timestamp - self->last) / 1000;
77 | self->depth -= self->depth > 0 ? 1 : 0;
78 | /* printf("<--- (%d) %s\r\n", self->depth, _current_module_str); */
79 | if (depth_matrix[current_module_str] > self->depth){
80 | depth_matrix[current_module_str] = 0;
81 | }
82 | }
83 |
84 | syscall:::entry
85 | /pid == $target/
86 | {
87 | @calls[basename(execname), "syscall", probefunc] = count();
88 | /* printf("\n@%s", probefunc); */
89 | ###SUPERVISED_MODULES_PROBES###
90 | }
91 |
92 | /*
93 | syscall:::return
94 | /pid == $target/
95 | {
96 | printf("\n");
97 | } */
98 |
99 |
100 | dtrace:::END
101 | {
102 | printf("System Calls (%d)\r\n\r\n", $target);
103 | printf(" %-32s %-10s %-22s %8s\r\n", "FILE", "TYPE", "NAME", "COUNT");
104 | printa(" %-32s %-10s %-22s %@8d\r\n", @calls);
105 | /* print(depth_matrix); */
106 | }
107 |
--------------------------------------------------------------------------------
/secimport/sandbox_helper.py:
--------------------------------------------------------------------------------
1 | """
2 | Import python modules with syscalls supervision.
3 |
4 | Copyright (c) 2022 Avi Lumelsky
5 | """
6 |
7 | from secimport.backends.common.instrumentation_backend import InstrumentationBackend
8 | from secimport.backends.common.utils import DEFAULT_BACKEND
9 |
10 |
11 | def secure_import(
12 | module_name: str,
13 | allow_shells: bool = False,
14 | allow_networking: bool = False,
15 | use_sudo: bool = False,
16 | log_python_calls: bool = False, # When True, the log file might reach GB in seconds.
17 | log_syscalls: bool = False,
18 | log_network: bool = False,
19 | log_file_system: bool = False,
20 | destructive: bool = True,
21 | backend=DEFAULT_BACKEND,
22 | **kwargs,
23 | ):
24 | """Import a python module in confined settings.
25 |
26 | Args:
27 | module_name (str): The name of the module to import. The same way you would have used an 'import' statement.
28 | allow_shells (bool, optional): Whether to allow forking/spwning shells and execute commands.
29 | allow_networking (bool, optional): Whether to allow socket syscalls.
30 | use_sudo (bool, optional): Whether to run dtrace with sudo. Can be turned off if sudo is not installed.
31 | log_python_calls (bool, optional): Whether to log the call tree of the python stack - function entry and exit events.
32 | log_network (bool, optional): Whether to log network successful connections - e.g socket syscalls.
33 | log_file_system (bool, optional): Whether to log filesystem calls - e.g read, open, write, etc.
34 | destructive (bool, optional): Whether to kill the process with -9 sigkill upon violation of any of the configurations above.
35 | Returns:
36 | _type_: A Python Module. The module is supervised by a dtrace process with destructive capabilities unless the 'destructive' argument is set to False.
37 | """
38 |
39 | if backend == InstrumentationBackend.EBPF:
40 | from secimport.backends.bpftrace_backend.bpftrace_backend import (
41 | run_bpftrace_script_for_module,
42 | )
43 |
44 | assert run_bpftrace_script_for_module(
45 | module_name=module_name,
46 | allow_shells=allow_shells,
47 | allow_networking=allow_networking,
48 | use_sudo=use_sudo,
49 | log_python_calls=log_python_calls,
50 | log_syscalls=log_syscalls,
51 | log_network=log_network,
52 | log_file_system=log_file_system,
53 | destructive=destructive,
54 | )
55 | elif backend == InstrumentationBackend.DTRACE:
56 | from secimport.backends.dtrace_backend.dtrace_backend import (
57 | run_dtrace_script_for_module,
58 | )
59 |
60 | assert run_dtrace_script_for_module(
61 | module_name=module_name,
62 | allow_shells=allow_shells,
63 | allow_networking=allow_networking,
64 | use_sudo=use_sudo,
65 | log_python_calls=log_python_calls,
66 | log_syscalls=log_syscalls,
67 | log_network=log_network,
68 | log_file_system=log_file_system,
69 | destructive=destructive,
70 | )
71 | else:
72 | raise NotImplementedError(f"backend '{backend}' is not implemented.")
73 |
74 | _module = __import__(module_name, **kwargs)
75 | return _module
76 |
--------------------------------------------------------------------------------
/docs/YAML_PROFILES.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
4 |
5 | - [YAML Profiles and Templates](#yaml-profiles-and-templates)
6 | - [Create a custom sandbox](#create-a-custom-sandbox)
7 | - [1. Generate a profile for your application](#1-generate-a-profile-for-your-application)
8 | - [2. Save the YAML output](#2-save-the-yaml-output)
9 | - [3. Build a sandbox (dtrace script) from a yaml file](#3-build-a-sandbox-dtrace-script-from-a-yaml-file)
10 | - [4. Run your app in your sandbox](#4-run-your-app-in-your-sandbox)
11 |
12 |
13 |
14 | # YAML Profiles and Templates
15 | It is very convenient to specify all of you policies, for all of your 3rd party and open source modules, in a single YAML file.
16 |
17 | # Create a custom sandbox
18 | 1. Generate a profile for your application using the tracing script.
19 | 2. Create a YAML security profile from your trace.
20 | 3. Build a sandbox (dtrace script) from the yaml file
21 | 1. Run your app in your sandbox!
22 |
23 | ## 1. Generate a profile for your application
24 | ```shell
25 | dtrace2) ➜ secimport git:(master) ✗ sudo dtrace -s secimport/templates/generate_profile.d -c "python -m http.server"
26 |
27 | Serving HTTP on :: port 8000 (http://[::]:8000/) ...
28 |
29 | CTRL + C
30 |
31 |
32 | Generated syscalls (yaml profile):
33 | destructive: true
34 | syscall_allowlist:
35 | - __mac_syscall
36 | - __pthread_canceled
37 | - bind
38 | - csrctl
39 | - fgetattrlist
40 | ...
41 | - read
42 | - ioctl
43 | - stat64
44 |
45 | Done.
46 | ```
47 |
48 | ## 2. Save the YAML output
49 | After yout hit CTRL + C, after "Generated syscalls (yaml profile)", the YAML profile is printed.
50 |
51 | An example for a template is available in example.yaml.
52 |
53 | It should look like this:
54 | ```shell
55 | # Example yaml file (with example syscalls)
56 |
57 | modules:
58 | requests:
59 | destructive: true
60 | syscall_allowlist:
61 | - write
62 | - ioctl
63 | - stat64
64 | fastapi:
65 | destructive: true
66 | syscall_allowlist:
67 | - bind
68 | - fchmod
69 | - stat64
70 | uvicorn:
71 | destructive: true
72 | syscall_allowlist:
73 | - getpeername
74 | - getpgrp
75 | - stat64
76 | ```
77 |
78 | Save the output of dtrace into a local YAML file and proceed to step 3.
79 |
80 | ## 3. Build a sandbox (dtrace script) from a yaml file
81 | Usage:
82 | ```shell
83 | python examples/create_profile_from_yaml.py
84 | ```
85 | Example:
86 | ```shell
87 | python examples/create_profile_from_yaml.py secimport/profiles/example.yaml /tmp/example.d
88 | ```
89 |
90 | Or, use the python API:
91 | ```python
92 | import secimport, pathlib
93 |
94 | template_path = pathlib.Path(secimport.sandbox_helper.PROFILES_DIR_NAME / 'example.yaml')
95 |
96 | sandbox_code = secimport.sandbox_helper.build_module_sandbox_from_yaml_template(template_path)
97 | ```
98 |
99 |
100 | ## 4. Run your app in your sandbox
101 | ```shell
102 | sudo dtrace -s /tmp/example.d -c "python"
103 | ```
104 |
105 | And that's it!
106 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/create_sandbox_from_yaml_or_json_policy/example.yaml:
--------------------------------------------------------------------------------
1 | modules:
2 | requests:
3 | destructive: true
4 | syscall_allowlist:
5 | - fchmod
6 | - getentropy
7 | - getpgrp
8 | - getrlimit
9 | - shm_open
10 | - sysctlbyname
11 | - access
12 | - munmap
13 | - issetugid
14 | - readlink
15 | - write
16 | - fcntl
17 | - fstatfs64
18 | - getdirentries64
19 | - mprotect
20 | - fcntl_nocancel
21 | - madvise
22 | - mmap
23 | - read_nocancel
24 | - select
25 | - sigprocmask
26 | - close_nocancel
27 | - write_nocancel
28 | - open_nocancel
29 | - close
30 | - open
31 | - sigaction
32 | - lseek
33 | - fstat64
34 | - read
35 | - ioctl
36 | - stat64
37 | fastapi:
38 | destructive: true
39 | syscall_allowlist:
40 | - bind
41 | - fchmod
42 | - getentropy
43 | - getpgrp
44 | - getrlimit
45 | - shm_open
46 | - sysctlbyname
47 | - access
48 | - munmap
49 | - issetugid
50 | - readlink
51 | - write
52 | - fcntl
53 | - fstatfs64
54 | - getdirentries64
55 | - mprotect
56 | - fcntl_nocancel
57 | - madvise
58 | - mmap
59 | - read_nocancel
60 | - select
61 | - sigprocmask
62 | - close_nocancel
63 | - write_nocancel
64 | - open_nocancel
65 | - close
66 | - open
67 | - sigaction
68 | - lseek
69 | - fstat64
70 | - read
71 | - ioctl
72 | - stat64
73 | - read
74 | - pipe
75 | - listen
76 | - poll
77 | - sigreturn
78 | - getsockname
79 | - kqueue
80 | - kevent
81 | - getpeername
82 | - getpgrp
83 | - listen
84 | - pipe
85 | - poll
86 | - setsockopt
87 | - shm_open
88 | - socket
89 | - socketpair
90 | - sysctlbyname
91 | - accept
92 | - access
93 | - getrlimit
94 | - kqueue
95 | - readlink
96 | - recvfrom
97 | - getsockname
98 | - issetugid
99 | - sendto
100 | - write
101 | - read_nocancel
102 | - getentropy
103 | - sigprocmask
104 | - fstatfs64
105 | - getdirentries64
106 | - munmap
107 | - madvise
108 | - select
109 | - write_nocancel
110 | - sigaction
111 | - fcntl_nocancel
112 | - fcntl
113 | - close_nocancel
114 | - open_nocancel
115 | - mprotect
116 | - mmap
117 | - kevent
118 | - open
119 | - close
120 | - lseek
121 | - ioctl
122 | - read
123 | - fstat64
124 | - stat64
125 | uvicorn:
126 | destructive: true
127 | syscall_allowlist:
128 | - getpeername
129 | - getpgrp
130 | - listen
131 | - pipe
132 | - poll
133 | - setsockopt
134 | - shm_open
135 | - socket
136 | - socketpair
137 | - sysctlbyname
138 | - accept
139 | - access
140 | - getrlimit
141 | - kqueue
142 | - readlink
143 | - recvfrom
144 | - getsockname
145 | - issetugid
146 | - sendto
147 | - write
148 | - read_nocancel
149 | - getentropy
150 | - sigprocmask
151 | - fstatfs64
152 | - getdirentries64
153 | - munmap
154 | - madvise
155 | - select
156 | - write_nocancel
157 | - sigaction
158 | - fcntl_nocancel
159 | - fcntl
160 | - close_nocancel
161 | - open_nocancel
162 | - mprotect
163 | - mmap
164 | - kevent
165 | - open
166 | - close
167 | - lseek
168 | - ioctl
169 | - read
170 | - fstat64
171 | - stat64
172 |
--------------------------------------------------------------------------------
/tests/test_bpftrace_backend.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import os
3 | import sys
4 |
5 |
6 | from secimport.backends.bpftrace_backend.bpftrace_backend import (
7 | render_bpftrace_template,
8 | create_bpftrace_script_for_module,
9 | run_bpftrace_script_for_module,
10 | )
11 | from secimport.backends.common.utils import BASE_DIR_NAME
12 |
13 |
14 | class TestEBPFBackend(unittest.TestCase):
15 | def setUp(self) -> None:
16 |
17 | try:
18 | os.remove(BASE_DIR_NAME)
19 | except (FileNotFoundError, PermissionError, IsADirectoryError):
20 | ...
21 | return super().setUp()
22 |
23 | def test_run_bpftrace_with_none_module_raises_error(self):
24 | with self.assertRaises(ModuleNotFoundError):
25 | create_bpftrace_script_for_module(
26 | module_name="nonexisting",
27 | allow_networking=False,
28 | allow_shells=False,
29 | log_file_system=True,
30 | log_syscalls=True,
31 | log_network=True,
32 | log_python_calls=True,
33 | destructive=True,
34 | )
35 |
36 | def test_create_bpftrace_script_for_module(self):
37 | bpftrace_script_file_path = create_bpftrace_script_for_module(
38 | "this",
39 | allow_networking=False,
40 | allow_shells=False,
41 | log_file_system=True,
42 | log_syscalls=True,
43 | log_network=True,
44 | log_python_calls=True,
45 | destructive=True,
46 | )
47 | self.assertEqual(
48 | bpftrace_script_file_path, "/tmp/.secimport/bpftrace_sandbox_this.bt"
49 | )
50 | self.assertTrue(os.path.exists(bpftrace_script_file_path))
51 | bpftrace_file_content = open(bpftrace_script_file_path).read()
52 | self.assertTrue("system" in bpftrace_file_content)
53 | self.assertTrue(sys.executable in bpftrace_file_content)
54 |
55 | def test_run_bpftrace_script_for_module(self):
56 | res = run_bpftrace_script_for_module(
57 | "this",
58 | allow_networking=False,
59 | allow_shells=False,
60 | log_file_system=True,
61 | log_syscalls=True,
62 | log_network=True,
63 | log_python_calls=True,
64 | destructive=True,
65 | dry_run=True,
66 | )
67 |
68 | self.assertTrue(res)
69 |
70 | def test_create_bpftrace_script_for_nonexisting_module(self):
71 | # create_bpftrace_script_for_module
72 | # TODO: implement
73 | bpftrace_script_file_path = create_bpftrace_script_for_module(
74 | module_name="urllib",
75 | allow_networking=False,
76 | allow_shells=False,
77 | log_file_system=True,
78 | log_syscalls=True,
79 | log_network=True,
80 | log_python_calls=True,
81 | destructive=True,
82 | )
83 | bpftrace_file_content = open(bpftrace_script_file_path).read()
84 | # Making sure all the template variables (start with '###') were successfully replaced in the function.
85 | self.assertTrue("###" not in bpftrace_file_content)
86 |
87 | def test_render_bpftrace_template(self):
88 | bpftrace_file_content = render_bpftrace_template(
89 | module_traced_name="http",
90 | allow_networking=False,
91 | allow_shells=False,
92 | log_file_system=True,
93 | log_syscalls=True,
94 | log_network=True,
95 | log_python_calls=True,
96 | destructive=True,
97 | )
98 | self.assertTrue("system(" in bpftrace_file_content)
99 | self.assertTrue("http" in bpftrace_file_content)
100 |
101 |
102 | if __name__ == "__main__":
103 | unittest.main()
104 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/fastapi/policy.json:
--------------------------------------------------------------------------------
1 | {
2 | "general_requirements": [
3 | " chdir",
4 | " clone",
5 | " epoll_ctl_old",
6 | " epoll_wait_old",
7 | " fork",
8 | " futimesat",
9 | " getcwd",
10 | " getdents",
11 | " getrusage",
12 | " gettimeofday",
13 | " mremap",
14 | " pipe2",
15 | " pread",
16 | " query_module",
17 | " shutdown",
18 | " sysinfo",
19 | " timer_create",
20 | " timer_delete",
21 | " uname",
22 | " vmsplice"
23 | ],
24 | "/root/.local/lib/python3.11/site-packages/pydantic/_internal/_g": [
25 | " epoll_wait_old"
26 | ],
27 | "/root/.local/lib/python3.11/site-packages/pydantic/fields.py": [
28 | " epoll_ctl_old"
29 | ],
30 | "/root/.local/lib/python3.11/site-packages/typing_extensions.py": [
31 | " chdir",
32 | " clone",
33 | " epoll_ctl_old",
34 | " epoll_wait_old",
35 | " fork",
36 | " getcwd",
37 | " kill",
38 | " remap_file_pages",
39 | " shmget",
40 | " timer_create",
41 | " timer_delete",
42 | " uname",
43 | " vmsplice",
44 | " wait4"
45 | ],
46 | "/workspace/Python-3.11.8/Lib/email/_policybase.py": [
47 | " chdir",
48 | " clone",
49 | " epoll_ctl_old",
50 | " epoll_wait_old",
51 | " fork",
52 | " getcwd",
53 | " kill",
54 | " shmget",
55 | " timer_create",
56 | " timer_delete",
57 | " uname",
58 | " wait4"
59 | ],
60 | "/workspace/Python-3.11.8/Lib/enum.py": [
61 | " chdir",
62 | " clone",
63 | " epoll_ctl_old",
64 | " epoll_wait_old",
65 | " fork",
66 | " getcwd",
67 | " kill",
68 | " shmget",
69 | " timer_create",
70 | " timer_delete",
71 | " uname",
72 | " vmsplice",
73 | " wait4"
74 | ],
75 | "/workspace/Python-3.11.8/Lib/types.py": [
76 | " chdir",
77 | " clone",
78 | " epoll_ctl_old",
79 | " fork",
80 | " getcwd",
81 | " kill",
82 | " shmget",
83 | " uname"
84 | ],
85 | "/workspace/Python-3.11.8/Lib/typing.py": [
86 | " access",
87 | " chdir",
88 | " clone",
89 | " epoll_ctl_old",
90 | " epoll_wait_old",
91 | " fork",
92 | " fremovexattr",
93 | " futimesat",
94 | " getcwd",
95 | " getrusage",
96 | " io_destroy",
97 | " io_getevents",
98 | " io_setup",
99 | " iopl",
100 | " kill",
101 | " lremovexattr",
102 | " mq_timedsend",
103 | " personality",
104 | " pipe",
105 | " pread",
106 | " query_module",
107 | " sched_getaffinity",
108 | " semget",
109 | " set_thread_area",
110 | " setrlimit",
111 | " shmget",
112 | " sysfs",
113 | " time",
114 | " timer_create",
115 | " timer_delete",
116 | " tkill",
117 | " uname",
118 | " uselib",
119 | " vmsplice",
120 | " wait4",
121 | " writev"
122 | ],
123 | "": [
124 | " chdir",
125 | " clone",
126 | " create_module",
127 | " delete_module",
128 | " epoll_ctl_old",
129 | " fork",
130 | " get_kernel_syms",
131 | " getcwd",
132 | " getdents",
133 | " init_module",
134 | " kill",
135 | " pread",
136 | " shmget",
137 | " uname",
138 | " wait4"
139 | ],
140 | "": [
141 | " chdir",
142 | " clone",
143 | " epoll_ctl_old",
144 | " fork",
145 | " getcwd",
146 | " kill",
147 | " mremap",
148 | " pread",
149 | " shmget",
150 | " timer_create",
151 | " uname",
152 | " uselib",
153 | " wait4"
154 | ]
155 | }
156 |
--------------------------------------------------------------------------------
/examples/cli/ebpf/fastapi/policy.yaml:
--------------------------------------------------------------------------------
1 | modules:
2 | /root/.local/lib/python3.11/site-packages/pydantic/_internal/_g:
3 | destructive: false
4 | syscall_allowlist:
5 | - ' epoll_wait_old'
6 | /root/.local/lib/python3.11/site-packages/pydantic/fields.py:
7 | destructive: false
8 | syscall_allowlist:
9 | - ' epoll_ctl_old'
10 | /root/.local/lib/python3.11/site-packages/typing_extensions.py:
11 | destructive: false
12 | syscall_allowlist:
13 | - ' chdir'
14 | - ' clone'
15 | - ' epoll_ctl_old'
16 | - ' epoll_wait_old'
17 | - ' fork'
18 | - ' getcwd'
19 | - ' kill'
20 | - ' remap_file_pages'
21 | - ' shmget'
22 | - ' timer_create'
23 | - ' timer_delete'
24 | - ' uname'
25 | - ' vmsplice'
26 | - ' wait4'
27 | /workspace/Python-3.11.8/Lib/email/_policybase.py:
28 | destructive: false
29 | syscall_allowlist:
30 | - ' chdir'
31 | - ' clone'
32 | - ' epoll_ctl_old'
33 | - ' epoll_wait_old'
34 | - ' fork'
35 | - ' getcwd'
36 | - ' kill'
37 | - ' shmget'
38 | - ' timer_create'
39 | - ' timer_delete'
40 | - ' uname'
41 | - ' wait4'
42 | /workspace/Python-3.11.8/Lib/enum.py:
43 | destructive: false
44 | syscall_allowlist:
45 | - ' chdir'
46 | - ' clone'
47 | - ' epoll_ctl_old'
48 | - ' epoll_wait_old'
49 | - ' fork'
50 | - ' getcwd'
51 | - ' kill'
52 | - ' shmget'
53 | - ' timer_create'
54 | - ' timer_delete'
55 | - ' uname'
56 | - ' vmsplice'
57 | - ' wait4'
58 | /workspace/Python-3.11.8/Lib/types.py:
59 | destructive: false
60 | syscall_allowlist:
61 | - ' chdir'
62 | - ' clone'
63 | - ' epoll_ctl_old'
64 | - ' fork'
65 | - ' getcwd'
66 | - ' kill'
67 | - ' shmget'
68 | - ' uname'
69 | /workspace/Python-3.11.8/Lib/typing.py:
70 | destructive: false
71 | syscall_allowlist:
72 | - ' access'
73 | - ' chdir'
74 | - ' clone'
75 | - ' epoll_ctl_old'
76 | - ' epoll_wait_old'
77 | - ' fork'
78 | - ' fremovexattr'
79 | - ' futimesat'
80 | - ' getcwd'
81 | - ' getrusage'
82 | - ' io_destroy'
83 | - ' io_getevents'
84 | - ' io_setup'
85 | - ' iopl'
86 | - ' kill'
87 | - ' lremovexattr'
88 | - ' mq_timedsend'
89 | - ' personality'
90 | - ' pipe'
91 | - ' pread'
92 | - ' query_module'
93 | - ' sched_getaffinity'
94 | - ' semget'
95 | - ' set_thread_area'
96 | - ' setrlimit'
97 | - ' shmget'
98 | - ' sysfs'
99 | - ' time'
100 | - ' timer_create'
101 | - ' timer_delete'
102 | - ' tkill'
103 | - ' uname'
104 | - ' uselib'
105 | - ' vmsplice'
106 | - ' wait4'
107 | - ' writev'
108 | :
109 | destructive: false
110 | syscall_allowlist:
111 | - ' chdir'
112 | - ' clone'
113 | - ' create_module'
114 | - ' delete_module'
115 | - ' epoll_ctl_old'
116 | - ' fork'
117 | - ' get_kernel_syms'
118 | - ' getcwd'
119 | - ' getdents'
120 | - ' init_module'
121 | - ' kill'
122 | - ' pread'
123 | - ' shmget'
124 | - ' uname'
125 | - ' wait4'
126 | :
127 | destructive: false
128 | syscall_allowlist:
129 | - ' chdir'
130 | - ' clone'
131 | - ' epoll_ctl_old'
132 | - ' fork'
133 | - ' getcwd'
134 | - ' kill'
135 | - ' mremap'
136 | - ' pread'
137 | - ' shmget'
138 | - ' timer_create'
139 | - ' uname'
140 | - ' uselib'
141 | - ' wait4'
142 | general_requirements:
143 | destructive: false
144 | syscall_allowlist:
145 | - ' chdir'
146 | - ' clone'
147 | - ' epoll_ctl_old'
148 | - ' epoll_wait_old'
149 | - ' fork'
150 | - ' futimesat'
151 | - ' getcwd'
152 | - ' getdents'
153 | - ' getrusage'
154 | - ' gettimeofday'
155 | - ' mremap'
156 | - ' pipe2'
157 | - ' pread'
158 | - ' query_module'
159 | - ' shutdown'
160 | - ' sysinfo'
161 | - ' timer_create'
162 | - ' timer_delete'
163 | - ' uname'
164 | - ' vmsplice'
165 |
--------------------------------------------------------------------------------
/docs/INSTALL.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
4 |
5 | - [Python Interpreter Requirements](#python-interpreter-requirements)
6 | - [OS requirements](#os-requirements)
7 | - [Install `bpftrace` (for Linux)](#install-bpftrace-for-linux)
8 | - [Install `dtrace` (for Mac, Solaris, Windows)](#install-dtrace-for-mac-solaris-windows)
9 | - [Using Docker: bpftrace (Linux, Mac)](#using-docker-bpftrace-linux-mac)
10 | - [Install Python with USDT probes and openssl (from source): ~5 minutes](#install-python-with-usdt-probes-and-openssl-from-source-5-minutes)
11 | - [Test the interpreter](#test-the-interpreter)
12 | - [Run `secimport` Tests](#run-secimport-tests)
13 | - [What's Next?](#whats-next)
14 |
15 |
16 |
17 |
18 | # Python Interpreter Requirements
19 | The only runtime requirement is a Python interpreter that was built with --with-dtrace (USDT probes).
20 | You can check if your current interpreter supported by running this command:
21 | ```python
22 | python -m sysconfig | grep WITH_DTRACE
23 | ```
24 | This configuration option should have the value "1" (and not "0"!):
25 | ```
26 | WITH_DTRACE = "1"
27 | ```
28 | If your current interpreter is not supported (empty output):
29 | - Using `pip`
30 | - `python3 -m pip install secimport`
31 | - Using `poetry`
32 | - `python3 -m pip install poetry && python3 -m poetry build`
33 |
34 |
35 | # OS requirements
36 | ## Install `bpftrace` (for Linux)
37 | Install bpftrace toolkit from https://github.com/iovisor/bpftrace/blob/master/INSTALL.md .
38 | Then, proceed to the python interpreter.
39 |
40 | ## Install `dtrace` (for Mac, Solaris, Windows)
41 | Some distributions include dtrace. check the `dtrace` command. If it is not installed:
42 | ```shell
43 | yum install dtrace-utils
44 | ```
45 |
46 | ### Using Docker: bpftrace (Linux, Mac)
47 | the `docker/` folder includes everything in the following guide.
48 | To build and run using docker, see Docker,
49 |
50 |
51 | # Install Python with USDT probes and openssl (from source): ~5 minutes
52 | If you want to use pip to work properly with pypi, you should also install openssl.
53 | To support ssl in this interpreter, One can simply install openssl pacakge using apt/yum/apk and it will use it automatically.
54 | If you wish to build openssl from source
55 |
56 | Download and build openssl
57 | ```shell
58 | wget https://www.openssl.org/source/openssl-1.1.1h.tar.gz
59 | tar -xvf openssl-1.1.1h.tar.gz
60 | cd openssl-1.1.1h
61 | ./config --prefix=$(pwd)/openssl --openssldir=$(pwd)/openssl
62 | make
63 | make test
64 | make install
65 | ```
66 |
67 | Download python
68 | ```shell
69 | PYTHON_VERSION="3.11.8"
70 |
71 | cd /tmp
72 | curl -o Python-$PYTHON_VERSION.tgz https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tgz
73 | tar -xzf Python-$PYTHON_VERSION.tgz
74 | ```
75 |
76 | If you want to use a custom OpenSSL version/installation, edit the `OPENSSL` variable in Modules/Setup:
77 |
78 | ```shell
79 | $ nano Python-$PYTHON_VERSION/Modules/Setup
80 |
81 | OPENSSL=/path/to/openssl-1.1.1h/openssl
82 | _ssl _ssl.c \
83 | -I$(OPENSSL)/include -L$(OPENSSL)/lib \
84 | -lssl -lcrypto
85 |
86 |
87 | # Configuring and install with dtrace, pip and openssl
88 | cd Python-$PYTHON_VERSION && ./configure --with-dtrace --prefix=$(pwd) --with-ensurepip=install && make
89 |
90 | # Optionsl: test your build
91 | make test
92 |
93 | # Optional: Install alongside your existing python without replacing it.
94 | make altinstall
95 | ```
96 |
97 |
98 | ## Test the interpreter
99 | ```shell
100 | ➜ Python-3.11.8 ./python
101 |
102 | Python 3.11.8 (default, Jul 6 2022, 09:21:12) [Clang 13.0.0 (clang-1300.0.27.3)] on darwin
103 | Type "help", "copyright", "credits" or "license" for more information.
104 | >>> import ssl
105 | >>> # You're good to go!
106 | ```
107 |
110 | ## Blocking New Processes Example
111 | ```python
112 | Python 3.11.8 (default, May 2 2022, 21:43:20) [Clang 13.0.0 (clang-1300.0.27.3)] on darwin
113 | Type "help", "copyright", "credits" or "license" for more information.
114 |
115 | # Let's import subprocess module, limiting it's syscall access.
116 | import secimport
117 | subprocess = secimport.secure_import("subprocess")
118 |
119 | # Let's import os
120 | import os
121 | os.system("ps")
122 | PID TTY TIME CMD
123 | 2022 ttys000 0:00.61 /bin/zsh -l
124 | 50092 ttys001 0:04.66 /bin/zsh -l
125 | 75860 ttys001 0:00.13 python
126 | 0
127 | # It worked as expected, returning exit code 0.
128 |
129 |
130 | # Now, let's try to invoke the same logic using a different module, "subprocess", that was imported using secure_import:
131 | subprocess.check_call('ps')
132 | [1] 75860 killed python
133 |
134 | # Damn! That's cool.
135 | ```
136 |
137 | When using secure_import, the following files are created:
138 | - The dtrace/bpftrace sandbox code for the module is saved under:
139 | - `/tmp/.secimport/sandbox_subprocess.bt`:
140 | - when using bpftrace
141 | - `/tmp/.secimport/sandbox_subprocess.d`
142 | - when using dtrace
143 | - The log file for this module is under
144 | - `/tmp/.secimport/sandbox_subprocess.log`:
145 | ```shell
146 | ...
147 |
148 | (OPENING SHELL using posix_spawn): (pid 75860) (thread 344676) (user 501) (python module: ) (probe mod=, name=entry, prov=syscall func=posix_spawn) /bin/sh
149 | #posix_spawn,
150 |
151 | (TOUCHING FILESYSTEM): write(140339021606912) from thread 344676
152 | libsystem_kernel.dylib`__fork+0xb
153 | _posixsubprocess.cpython-310-darwin.so`do_fork_exec+0x29
154 | _posixsubprocess.cpython-310-darwin.so`subprocess_fork_exec+0x71f
155 | python.exe`cfunction_call+0x86
156 | killing...
157 | killed.
158 | ```
159 |
160 | ## Shell Blocking Example
161 | You can try it yourself with docker, locally.
162 | ```
163 | ./docker/build.sh
164 | ./docker/run.sh
165 | root@d57458518cbf:/workspace$ ./run_sandbox.sh
166 | 🚀 Starting secimport sandbox with bpftrace backend, the sandbox should kill the python process...
167 | WARNING: Addrspace is not set
168 | PID TTY TIME CMD
169 | 1 pts/0 00:00:00 sh
170 | 10 pts/0 00:00:00 bash
171 | 18 pts/0 00:00:00 bash
172 | 19 pts/0 00:00:00 bpftrace
173 | 23 pts/0 00:00:00 python
174 | 24 pts/0 00:00:00 sh
175 | 25 pts/0 00:00:00 sh
176 | 26 pts/0 00:00:00 pkill
177 | 27 pts/0 00:00:00 ps
178 |
179 |
180 | 🛑 The process was killed, as expected.
181 | 🚀 The sandbox bpftrace code is at sandbox.bt
182 | 🚀 The sandbox log is at sandbox.log.
183 | ```
184 |
185 | The following code will open a new bash shell using os.system.
186 | In this sandbox we disallow such behavior, by disabling the fork/exec/spawn syscalls.
187 |
188 | ```python
189 | # example.py - Executes code upon import;
190 | import os;
191 |
192 | os.system('Hello World!');
193 | ```
194 | ```python
195 | from secimport import secure_import
196 |
197 | example = secure_import('example', allow_shells=False)
198 | ```
199 | Let's run the and see what happens:
200 | ```
201 | (root) sh-3.2# export PYTHONPATH=$(pwd):$(pwd)/examples:$(pwd):$PYTHONPATH
202 | (root) sh-3.2# python examples/production.py
203 | Successfully compiled dtrace profile: /tmp/.secimport/sandbox_example.d
204 | Killed: 9
205 | ```
206 | - We imported `example` with limited capabilities.
207 | - If a syscall like `spawn/exec/fork/forkexec` will be executed
208 | - The process will be `kill`ed with `-9` signal.
209 |