diff --git a/README.md b/README.md
index 3ea07cf..9ee83af 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,41 @@
# secimport
+
-Secure import for python modules using dtrace under the hood.
+An easy way to constrain python modules in your code using backends like bpftrace (eBPF) and dtrace.
Medium Article
`secimport` can be used to:
- Confine/Restrict specific python modules inside your production environment.
- - Open Source, 3rd party from unstrusted sources.
- - Audit the flow of your python application at user-space/os/kernel level.
-- Run an entire python application under unified configuration
- - Like `seccomp` but not limited to Linux kernels. Cross platform.
-
+ - Restrict 3rd party or open source modules in your code.
+- Audit the flow of your python application at user-space/os/kernel level.
+- Kill the process upon violoation of a profile.
# Quick Start
`secimport` can be used out of the box in the following ways:
-1. Inside your code using `module = secimport.secure_import('module_name', ...)`.
- - Replacing the regular `import` statement with `secure_import`
- - Only modules that were imported with `secure_import` will be traced.
-2. As a sandbox, by specifying the modules and their policies.
- - Use this repository to:
- - Generate a YAML policy from your code
- - Compile that YAML to dscript.
- - Use `dtrace` command to run your main python application, with your tailor-made sandbox.
- - No need for `secure_import`, you can keep using regular `import`s
+1. Modify your imports
+ - Inside your code using `module = secimport.secure_import('module_name', ...)`.
+ - Replacing the regular `import` statement with `secure_import`
+ - Only modules that were imported with `secure_import` will be traced.
+2. As a sandbox that runs your main code.
+ 1. Generate a YAML policy from your code, by specifying the modules and the policy you want for each module you use.
+ 2. Convert that YAML policy to dscript/bpftrace sandbox code.
+ 3. Use `dtrace` or `ebpf` to run your main python application, with your tailor-made sandbox.
+ - No need for `secure_import`, you can keep using regular `import`s and not change your code at all.
For the full list of examples, see EXAMPLES.md.
-# Pickle Example
+# Docker
+A working environment is not easy to create.
+The easiest way to evaluate secimport, is by using our Docker for MacOS and Linux that includes secimport, bpftrace backend and eBPF libraries.
+dtrace backend is not available in docker, and can be tried directly on the compatible hosts (MacOS, Solaris, Unix, some Linux distributions)
+
+# Use Cases
+
### How pickle can be exploited in your 3rd party packages:
```python
>>> import pickle
@@ -72,7 +77,7 @@ $ less /tmp/.secimport/sandbox_pickle.log
:
```
-## YAML Template Example
+## YAML Policy Example
For a full tutorial, see YAML Profiles Usage
```shell
# An example yaml template for a sandbox.
@@ -102,7 +107,7 @@ modules:
```
-## Python Processing Example
+## Blocking New Processes Example
```python
Python 3.10.0 (default, May 2 2022, 21:43:20) [Clang 13.0.0 (clang-1300.0.27.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
@@ -129,8 +134,12 @@ Type "help", "copyright", "credits" or "license" for more information.
# Damn! That's cool.
```
-- The dtrace profile for the module is saved under:
- - `/tmp/.secimport/sandbox_subprocess.d`:
+When using secure_import, the following files are created:
+- The dtrace/bpftrace sandbox code for the module is saved under:
+ - `/tmp/.secimport/sandbox_subprocess.d`
+ - when using dtrace
+ - `/tmp/.secimport/sandbox_subprocess.bt`:
+ - when using bpftrace
- The log file for this module is under
- `/tmp/.secimport/sandbox_subprocess.log`:
```shell
@@ -216,8 +225,9 @@ Not related for python, but for the sake of explanation (Equivilant Demo soon).
- Tracing Guides
- F.A.Q
- Installation
-- Mac OS Users - Disabling SIP for dtrace
+- Mac OS Users - Disabling SIP (System Intergity Protection)
- https://www.brendangregg.com/DTrace/DTrace-cheatsheet.pdf
+- https://www.brendangregg.com/blog/2018-10-08/dtrace-for-linux-2018.html
## TODO:
@@ -226,6 +236,14 @@ Not related for python, but for the sake of explanation (Equivilant Demo soon).
- ✔️ Use secimport to compile that yml
- ✔️ Create a single dcript policy
- ✔️ Run an application with that policy using dtrace, without using `secure_import`
-- Node support (dtrace hooks)
-- Go support (dtrace hooks)
-- Use current_module_str together with thread ID
+- ✔️ Add eBPF basic support using bpftrace
+ - ✔️ bpftrace backend tests
+- Extandible Language Template
+ - Increase extandability for new languages tracing with bpftace/dtrace.
+ - Adding a new integration will be easy, in a single directory, using templates for filters, actions, etc.
+- Node support (bpftrace/dtrace hooks)
+ - Implement a template for Node's call stack and event loop
+- Go support (bpftrace/dtrace hooks)
+ - Implement a template for golang's call stack
+- Multi Process support: Use current_module_str together with thread ID to distinguish between events in different processes
+- Update all linux syscalls in the templates (filesystem, networking, processing) to improve the sandbox blocking of unknowns.
diff --git a/docker/README.md b/docker/README.md
new file mode 100755
index 0000000..8f1a98c
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,30 @@
+# Try secimport with bpftrace
+
+## How to Use
+
+1. Install Docker: https://docs.docker.com/get-docker
+2. ./build.sh
+ - will build a docker image with
+ - python with dtrace static USDT instrumentations
+ - bpftrace
+ - secimport code
+ - ~1GB in size
+3. ./run.sh
+ - Runs temporary example sandbox using bpftrace
+ - Then, it will execute os.system('ps').
+ - the process should be killed.
+ - Once the process is killed, it prints the logs of the sandbox.
+
+
+## FAQ
+
+### How it runs on macOS?
+- The Docker for mac runs Linux on a hypervisor called hyperkit, and docker runs inside it, so you can use Linux features.
+
+### Can we trace a macOS host with this docker?
+- Not at the moment. The bpftrace runs inside a Linux VM.
+- For macOS, there is dtrace.
+
+=====================
+
+Based on the great example repo: https://github.com/mmisono/try-bpftrace-in-mac
diff --git a/docker/build.sh b/docker/build.sh
new file mode 100755
index 0000000..b540ac5
--- /dev/null
+++ b/docker/build.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+if [[ "$PWD" =~ docker$ ]]
+then
+ echo "Building secimport docker container...";
+else
+ echo "Please run this script from the secimport/docker directory.";
+ exit 1;
+fi
+
+# linukit kernel version
+KERNEL_VERSION=`docker run --rm -it alpine uname -r | cut -d'-' -f1`
+BPFTRACE_VERSION=${BPFTRACE_VERSION:-v0.16.0}
+PYTHON_VERSION=${PYTHON_VERSION:-"3.10.0"}
+
+pushd docker
+
+docker build \
+ --build-arg KERNEL_VERSION=${KERNEL_VERSION} \
+ --build-arg BPFTRACE_VERSION=${BPFTRACE_VERSION} \
+ --build-arg PYTHON_VERSION=${PYTHON_VERSION} \
+ -t secimport:${KERNEL_VERSION} .
+
+popd
+
+echo "You can now use the ./run.sh script to try secimport."
\ No newline at end of file
diff --git a/docker/docker/Dockerfile b/docker/docker/Dockerfile
new file mode 100755
index 0000000..cf653fc
--- /dev/null
+++ b/docker/docker/Dockerfile
@@ -0,0 +1,38 @@
+ARG KERNEL_VERSION
+
+FROM linuxkit/kernel:${KERNEL_VERSION} as ksrc
+FROM ubuntu:20.04 AS build
+
+ARG BPFTRACE_VERSION
+ARG PYTHON_VERSION
+
+WORKDIR /kernel
+COPY --from=ksrc /kernel-dev.tar .
+RUN tar xf kernel-dev.tar
+
+WORKDIR /workspace
+ARG DEBIAN_FRONTEND=noninteractive
+
+# TODO: add openssl (longer build time, but pip will work for our interpreter)
+RUN echo "Installing prerequisites" && \
+ apt-get update && apt-get install sudo build-essential 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
+RUN echo "Installing python with dtrace" && \
+ curl -o Python-${PYTHON_VERSION}.tgz https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz && tar -xzf Python-${PYTHON_VERSION}.tgz && \
+ cd Python-${PYTHON_VERSION} && ./configure --with-dtrace --prefix=/usr/local/openssl --prefix=$(pwd) --with-ensurepip=install && make && make install
+RUN echo "Installing bpftrace" && \
+ wget https://github.com/iovisor/bpftrace/releases/download/${BPFTRACE_VERSION}/bpftrace && \
+ chmod +x bpftrace && \
+ mv bpftrace /bin && \
+ wget https://github.com/iovisor/bpftrace/archive/${BPFTRACE_VERSION}.zip && \
+ unzip ${BPFTRACE_VERSION}.zip && \
+ cp -r bpftrace*/tools /workspace/bpftrace/ && \
+ echo "Done building bpftrace" && \
+ mv /kernel/usr/src/linux-headers* /kernel/usr/src/linux-headers
+
+ENV BPFTRACE_KERNEL_SOURCE=/kernel/usr/src/linux-headers
+COPY setup.sh .
+COPY sandbox.bt .
+COPY run_sandbox.sh .
+RUN chmod 755 sandbox.bt run_sandbox.sh
+
+ENTRYPOINT ["/bin/sh", "/workspace/setup.sh"]
diff --git a/docker/docker/run_sandbox.sh b/docker/docker/run_sandbox.sh
new file mode 100755
index 0000000..461a4b2
--- /dev/null
+++ b/docker/docker/run_sandbox.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+# "--unsafe" is required to run system command for remediation.
+# If process termination on violoation is not needed,
+# You can remove this argument.
+
+echo "Starting secimport sandbox with bpftrace backend, the sandbox should kill the python process..."
+bpftrace -c "/workspace/Python-3.10.0/python -c __import__('os').system('ps')" -o sandbox.log sandbox.bt --unsafe || echo "The process was killed, as expected."
+echo "The sandbox bpftrace code is at sandbox.bt"
+echo "The sandbox log is at sandbox.log"
+# tail -n 20 sandbox.log
+# less +G sandbox.log
\ No newline at end of file
diff --git a/docker/docker/sandbox.bt b/docker/docker/sandbox.bt
new file mode 100755
index 0000000..890e321
--- /dev/null
+++ b/docker/docker/sandbox.bt
@@ -0,0 +1,32 @@
+#!/usr/bin/env bpftrace
+
+BEGIN {
+ printf("STARTED\n")
+}
+
+
+usdt:/workspace/Python-3.10.0/python:function__entry {
+ @["depth"]++;
+ @entrypoints[str(arg0)] = @["depth"];
+ @globals["previous_module"] = @globals["current_module"];
+ @globals["current_module"] = str(arg0);
+ printf("%s, %s, depth=%d\n", str(arg0), str(arg1), @["depth"]) ;
+}
+
+usdt:/workspace/Python-3.10.0/python:function__return {
+ @["depth"]--;
+}
+
+tracepoint:raw_syscalls:sys_enter /comm == "python"/ {
+ if(args->id == 59){
+ printf("KILLING PROCESS %s - EXECUTED execve;\n", str(pid));
+ system("pkill -9 args"); // optional
+ printf("Killed process %s", str(pid));
+ exit(); // optional
+ }
+ printf("%s SYSCALL %ld depth=%d previous=%s current=%s \n", probe, args->id, @["depth"], @globals["previous_module"], @globals["current_module"] );
+}
+
+END {
+ clear(@entrypoints);
+}
diff --git a/docker/docker/setup.sh b/docker/docker/setup.sh
new file mode 100755
index 0000000..d0d912d
--- /dev/null
+++ b/docker/docker/setup.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+mount -t debugfs none /sys/kernel/debug/
+sysctl -w kernel.kptr_restrict=0 >/dev/null 2>&1
+sysctl -w kernel.perf_event_paranoid=2 >/dev/null 2>&1
+cd /workspace/
+/bin/bash
+# /bin/bash ./run_sandbox.sh
\ No newline at end of file
diff --git a/docker/run.sh b/docker/run.sh
new file mode 100755
index 0000000..dcbaef6
--- /dev/null
+++ b/docker/run.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+if [[ "$PWD" =~ docker$ ]]
+then
+ echo "Running secimport docker container...";
+else
+ echo "Please run this script from the secimport/docker directory.";
+ exit 1;
+fi
+
+KERNEL_VERSION=`docker run --rm -it alpine uname -r | cut -d'-' -f1`
+
+cd .. # back to repo root dir
+docker run --rm --name=secimport --privileged -v "$(pwd)/src/secimport":"/workspace/secimport/" -it secimport:${KERNEL_VERSION}
diff --git a/docs/TRACING_PROCESSES.md b/docs/TRACING_PROCESSES.md
index 34b28c4..7f88e26 100644
--- a/docs/TRACING_PROCESSES.md
+++ b/docs/TRACING_PROCESSES.md
@@ -10,12 +10,14 @@ There are several ways to create a secimport profile for your modules.
- `sudo dtrace -s src/secimport/templates/default.allowlist.template.d -c "python -m http.server"`
- CTRL+C
- Create a secure import based on that log.
- - Using simple `dtrace`
+ - Using `bpftrace`
+ - See https://github.com/iovisor/bpftrace/tree/master/tools
+ - Using `dtrace`
- Tracing the syscalls of a process with pid `12345`
- `dtrace -n 'syscall::: /pid == ($1)/ {@[pid,execname,probefunc]=count()}' 12345`
- Tracing the syscalls of a docker container with pid `12345`
- `dtrace -n 'syscall::: /progenyof($1)/ {@[pid,execname,probefunc]=count()}' 12345`
- - Using `strace`
+ - Using an `strace` script I contributed to FireJail
- A script to list all your application's syscalls using `strace`.
I contributed it to `firejail` a few years ago:
- https://github.com/netblue30/firejail/blob/master/contrib/syscalls.sh
- ```
diff --git a/examples/run_bpftrace_example.sh b/examples/run_bpftrace_example.sh
new file mode 100644
index 0000000..404573b
--- /dev/null
+++ b/examples/run_bpftrace_example.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+# "--unsafe" is required to run system command for remediation.
+# If process termination on violoation is not needed,
+# You can remove this argument.
+
+echo "Starting secimport sandbox with python shell..."
+bpftrace -c "/workspace/Python-3.10.0/python -c __import__('os').system('ps')" -o sandbox.log sandbox.bt --unsafe
+
+# The process is killed becused we ran os.system inside our sandbox.
+# Watch the logs:
+less +G sandbox.log
+# OR:
+# tail -n 20 sandbox.log
+echo "The sandbox log is at ./sandbox.log"
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 952e4ef..0a7ff5d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "secimport"
-version = "0.4.2"
+version = "0.5.0"
description = "A sandbox/supervisor for python modules."
authors = ["Avi Lumelsky"]
license = "MIT"
diff --git a/src/secimport/backends/bpftrace_backend/__init__.py b/src/secimport/backends/bpftrace_backend/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/secimport/backends/bpftrace_backend/bpftrace_backend.py b/src/secimport/backends/bpftrace_backend/bpftrace_backend.py
new file mode 100644
index 0000000..3dc4373
--- /dev/null
+++ b/src/secimport/backends/bpftrace_backend/bpftrace_backend.py
@@ -0,0 +1,330 @@
+"""
+Import python modules with bpftrace supervision.
+This is a direct copy/pase from dtrace backend,
+TODO: abstract base class for backend, inclusing this supervisor/sandbox backend API.
+
+Copyright (c) 2022 Avi Lumelsky
+"""
+
+import importlib
+import os
+from sys import executable as PYTHON_EXECUTABLE
+import stat
+import time
+from pathlib import Path
+from typing import List
+
+from secimport.backends.common.instrumentation_backend import InstrumentationBackend
+from secimport.backends.common.utils import (
+ BASE_DIR_NAME,
+ SECIMPORT_ROOT,
+ render_syscalls_filter,
+)
+
+TEMPLATES_DIR_NAME = SECIMPORT_ROOT / "templates" / "bpftrace"
+
+
+def create_bpftrace_script_for_module(
+ module_name: str,
+ allow_shells: bool,
+ allow_networking: bool,
+ log_python_calls: bool,
+ log_syscalls: bool,
+ log_network: bool,
+ log_file_system: bool,
+ destructive: bool,
+ syscalls_allowlist: List[str] = None,
+ syscalls_blocklist: List[str] = None,
+ templates_dir: Path = TEMPLATES_DIR_NAME,
+) -> Path:
+ """
+ # Template components available at the moment:
+ # ###DESTRUCTIVE###
+ # ###FUNCTION_ENTRY###
+ # ###FUNCTION_EXIT###
+ # ###SYSCALL_ENTRY###
+ # ###MODULE_NAME###
+ """
+
+ module = importlib.machinery.PathFinder().find_spec(module_name)
+ if module is None:
+ raise ModuleNotFoundError(module)
+ module_traced_name = module.origin # e.g this.py
+
+ assert not (
+ syscalls_allowlist is not None and syscalls_blocklist is not None
+ ), "Please specify either syscalls_allowlist OR syscalls_blocklist."
+
+ # If we have an allowlist
+ if syscalls_allowlist is not None:
+ script_template = render_allowlist_template(
+ module_name=module_name,
+ destructive=destructive,
+ syscalls_allowlist=syscalls_allowlist,
+ templates_dir=templates_dir,
+ )
+ elif syscalls_blocklist is not None:
+ script_template = render_blocklist_template(
+ module_name=module_name,
+ destructive=destructive,
+ syscalls_blocklist=syscalls_blocklist,
+ templates_dir=templates_dir,
+ )
+ else:
+ script_template = render_bpftrace_template(
+ module_traced_name=module_traced_name,
+ allow_shells=allow_shells,
+ allow_networking=allow_networking,
+ log_python_calls=log_python_calls,
+ log_syscalls=log_syscalls,
+ log_network=log_network,
+ log_file_system=log_file_system,
+ destructive=destructive,
+ templates_dir=templates_dir,
+ )
+
+ # Creating a dscript file with the modified template
+ if not os.path.exists(BASE_DIR_NAME):
+ os.mkdir(BASE_DIR_NAME)
+
+ module_file_name = os.path.join(BASE_DIR_NAME, f"bpftrace_sandbox_{module_name}.bt")
+ with open(module_file_name, "w") as module_file:
+ module_file.write(script_template)
+
+ # TODO: FIGURE OUT A WAY TO COMPILE WITHOUT EXECUTING - MSKING SURE THE GENERATION WORKED SYNTAX-WISE.
+ return module_file_name
+
+
+def run_bpftrace_script_for_module(
+ module_name: str,
+ allow_shells: bool,
+ allow_networking: bool,
+ log_python_calls: bool,
+ log_syscalls: bool,
+ log_network: bool,
+ log_file_system: bool,
+ destructive: bool,
+ syscalls_allowlist: List[str],
+ syscalls_blocklist: List[str],
+ use_sudo: bool = False,
+ templates_dir: Path = TEMPLATES_DIR_NAME,
+):
+ module_file_path = create_bpftrace_script_for_module(
+ module_name=module_name,
+ allow_shells=allow_shells,
+ allow_networking=allow_networking,
+ log_python_calls=log_python_calls,
+ log_syscalls=log_syscalls,
+ log_network=log_network,
+ log_file_system=log_file_system,
+ destructive=destructive,
+ syscalls_allowlist=syscalls_allowlist,
+ syscalls_blocklist=syscalls_blocklist,
+ templates_dir=templates_dir,
+ )
+ output_file = BASE_DIR_NAME / f"bpftrace_sandbox_{module_name}.log"
+ current_pid = os.getpid()
+ bpftrace_command = f'{"sudo " if use_sudo else ""} {module_file_path} --unsafe -p {current_pid} -o {output_file} &2>/dev/null'
+ st = os.stat(module_file_path)
+ os.chmod(module_file_path, st.st_mode | stat.S_IEXEC)
+ print("(running bpftrace supervisor): ", bpftrace_command)
+ os.system(bpftrace_command)
+ time.sleep(5) # TODO: change from 5 seconds (wait) to fd creation (event)
+ return True
+
+
+def render_allowlist_template(
+ module_name: str,
+ destructive: bool,
+ syscalls_allowlist: List[str],
+ templates_dir: Path = TEMPLATES_DIR_NAME,
+):
+ return render_bpftrace_probe_for_module(
+ module_name=module_name,
+ destructive=destructive,
+ syscalls_list=syscalls_allowlist,
+ syscalls_allow=True,
+ templates_dir=templates_dir,
+ )
+
+
+def render_blocklist_template(
+ module_name: str,
+ destructive: bool,
+ syscalls_blocklist: List[str],
+ templates_dir: Path = TEMPLATES_DIR_NAME,
+):
+ return render_bpftrace_probe_for_module(
+ module_name=module_name,
+ destructive=destructive,
+ syscalls_list=syscalls_blocklist,
+ syscalls_allow=False,
+ templates_dir=templates_dir,
+ )
+
+
+def render_bpftrace_template(
+ module_traced_name: str,
+ allow_shells: bool,
+ allow_networking: bool,
+ log_python_calls: bool,
+ log_syscalls: bool,
+ log_network: bool,
+ log_file_system: bool,
+ destructive: bool,
+ templates_dir: Path = TEMPLATES_DIR_NAME,
+ default_template_filename: str = "default.template.bt",
+ interpreter_path: str = PYTHON_EXECUTABLE,
+):
+ script_template = open(
+ templates_dir / default_template_filename,
+ "r",
+ ).read()
+
+ # Updating the right binary to be probed by bpftrace
+ script_template = script_template.replace(
+ "###INTERPRETER_PATH###", interpreter_path
+ )
+
+ # PYTHON instrumentations
+ code_syscall_entry = ""
+ if log_python_calls is True:
+ code_function_entry = open(
+ templates_dir / "actions/log_python_module_entry.bt",
+ "r",
+ ).read()
+ code_function_exit = open(
+ templates_dir / "actions/log_python_module_exit.bt",
+ "r",
+ ).read()
+ script_template = script_template.replace(
+ "###FUNCTION_ENTRY###", code_function_entry
+ )
+ script_template = script_template.replace(
+ "###FUNCTION_EXIT###", code_function_exit
+ )
+ else:
+ script_template = script_template.replace("###FUNCTION_ENTRY###", "")
+ script_template = script_template.replace("###FUNCTION_EXIT###", "")
+
+ # SYSCALLS instrumentations
+ if log_syscalls is True:
+ _code = open(
+ templates_dir / "actions/log_syscall.bt",
+ "r",
+ ).read()
+ code_syscall_entry += f"{_code};\n"
+
+ if log_file_system is True:
+ filter_fs_code = open(
+ templates_dir / "filters/file_system.bt",
+ "r",
+ ).read()
+ code_syscall_entry += f"{filter_fs_code}\n{{"
+ action_log_file_system = open(
+ templates_dir / "actions/log_file_system.bt",
+ "r",
+ ).read()
+ code_syscall_entry += f"{action_log_file_system}}}\n"
+
+ if allow_networking is False or log_network is True:
+ filter_networking_code = open(
+ templates_dir / "filters/networking.bt",
+ "r",
+ ).read()
+ code_syscall_entry += f"{filter_networking_code}{{\n"
+
+ if log_network is True:
+ action_log_network = open(
+ templates_dir / "actions/log_network.bt",
+ "r",
+ ).read()
+ code_syscall_entry += f"{action_log_network}\n"
+
+ if allow_networking is False:
+ action_kill = open(
+ templates_dir / "actions/kill_process.bt",
+ "r",
+ ).read()
+ code_syscall_entry += f"{action_kill}\n"
+ code_syscall_entry += "}\n"
+
+ if allow_shells is False:
+ filter_processes_code = open(
+ templates_dir / "filters/processes.bt",
+ "r",
+ ).read()
+ code_syscall_entry += f"{filter_processes_code}{{\n"
+ action_kill_on_processing = open(
+ templates_dir / "actions/kill_on_processing.bt",
+ "r",
+ ).read()
+ code_syscall_entry += f"{action_kill_on_processing}}}\n"
+
+ script_template = script_template.replace("###SYSCALL_ENTRY###", code_syscall_entry)
+ script_template = script_template.replace("###MODULE_NAME###", module_traced_name)
+
+ # Updating the destructive behavior
+ script_template = script_template.replace(
+ "###DESTRUCTIVE###", "1" if destructive else "0"
+ )
+ return script_template
+
+
+def render_bpftrace_probe_for_module(
+ module_name: str,
+ destructive: bool,
+ syscalls_list: List[str],
+ syscalls_allow: bool,
+ templates_dir: Path = TEMPLATES_DIR_NAME,
+) -> str:
+ # Loading the probe allowlist template
+ probe_template = open(
+ templates_dir / "probes/module_syscalls_allowlist_template.bt",
+ "r",
+ ).read()
+
+ # Adding a syscalls filter
+ syscalls_filter = render_syscalls_filter(
+ syscalls_list=syscalls_list,
+ allow=syscalls_allow,
+ instrumentation_backend=InstrumentationBackend.EBPF,
+ )
+ probe_template = probe_template.replace("###SYSCALL_FILTER###", syscalls_filter)
+
+ # Adding a probe filter for the specified python module
+ supervision_filter = open(
+ templates_dir / "filters/is_current_module_under_supervision.bt",
+ "r",
+ ).read()
+
+ # Adding the action according to the 'destructive' flag: kill if destructive, log otherwise
+ supervision_action = ""
+ if destructive is True:
+ action_kill_process = open(
+ templates_dir / "actions/kill_process.bt",
+ "r",
+ ).read()
+ supervision_action = f"{{{action_kill_process}}}\n"
+ else:
+ action_log_syscall = open(
+ templates_dir / "actions/log_syscall.bt",
+ "r",
+ ).read()
+ supervision_action = f"{{{action_log_syscall}}}\n"
+
+ # Updating the template with the filters, their actions, and module name
+ probe_template = probe_template.replace(
+ "###SUPERVISED_MODULES_FILTER###", supervision_filter
+ )
+ probe_template = probe_template.replace(
+ "###SUPERVISED_MODULES_ACTION###", supervision_action
+ )
+ probe_template = probe_template.replace("###MODULE_NAME###", module_name)
+
+ # Updating the destructive behavior
+ probe_template = probe_template.replace(
+ "###DESTRUCTIVE###", "true" if destructive else "false"
+ )
+
+ return probe_template
diff --git a/src/secimport/backends/common/__init__.py b/src/secimport/backends/common/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/secimport/backends/common/instrumentation_backend.py b/src/secimport/backends/common/instrumentation_backend.py
new file mode 100644
index 0000000..1f92dcd
--- /dev/null
+++ b/src/secimport/backends/common/instrumentation_backend.py
@@ -0,0 +1,6 @@
+from enum import Enum
+
+
+class InstrumentationBackend(Enum):
+ DTRACE = "dtrace"
+ EBPF = "ebpf" # bpftrace
diff --git a/src/secimport/backends/common/utils.py b/src/secimport/backends/common/utils.py
new file mode 100644
index 0000000..a3a65f9
--- /dev/null
+++ b/src/secimport/backends/common/utils.py
@@ -0,0 +1,55 @@
+import os
+from sys import platform
+from pathlib import Path
+from typing import List
+
+from secimport.backends.common.instrumentation_backend import InstrumentationBackend
+
+
+BASE_DIR_NAME = Path("/tmp/.secimport")
+SECIMPORT_ROOT = Path(
+ os.path.realpath(
+ os.path.split(__file__)[:-1][0] + os.sep + os.pardir + os.sep + os.pardir
+ )
+)
+DEFAULT_BACKEND = None
+
+if "linux" in platform.lower():
+ TEMPLATES_DIR_NAME = SECIMPORT_ROOT / "templates" / "bpftrace"
+ DEFAULT_BACKEND = InstrumentationBackend.EBPF
+ # TODO: verify bpftrace is installed, if not, link to the repo documentation on how to install.
+else:
+ TEMPLATES_DIR_NAME = SECIMPORT_ROOT / "templates" / "dtrace"
+ DEFAULT_BACKEND = InstrumentationBackend.DTRACE
+
+PROFILES_DIR_NAME = SECIMPORT_ROOT / "profiles"
+
+
+def render_syscalls_filter(
+ syscalls_list: List[str],
+ allow: bool,
+ instrumentation_backend: InstrumentationBackend,
+):
+ assert isinstance(allow, bool), '"allow" must be a bool value'
+ # "=="" means the syscall matches (blocklist), while "!="" means allow only the following.
+ match_sign = "!=" if allow else "=="
+ syscalls_filter = ""
+ for i, _syscall in enumerate(syscalls_list):
+ if i > 0:
+ syscalls_filter += " && "
+ assert isinstance(
+ _syscall, str
+ ), f"The provided syscall it not a syscall string name: {_syscall}"
+
+ if instrumentation_backend == InstrumentationBackend.DTRACE:
+ syscalls_filter += f'probefunc {match_sign} "{_syscall}"'
+ elif instrumentation_backend == InstrumentationBackend.EBPF:
+ syscalls_filter += f'@sysname[args->id] {match_sign} "{_syscall}"'
+ else:
+ raise NotImplementedError(
+ f"backend '{instrumentation_backend}' is not supported"
+ )
+
+ filter_name = "allowlist" if allow else "blocklist"
+ print(f"Adding syscall {_syscall} to {filter_name}")
+ return syscalls_filter
diff --git a/src/secimport/backends/dtrace_backend/__init__.py b/src/secimport/backends/dtrace_backend/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/secimport/backends/dtrace_backend/dtrace_backend.py b/src/secimport/backends/dtrace_backend/dtrace_backend.py
new file mode 100644
index 0000000..cdde4b2
--- /dev/null
+++ b/src/secimport/backends/dtrace_backend/dtrace_backend.py
@@ -0,0 +1,397 @@
+"""
+Import python modules with dtrace supervision.
+
+Copyright (c) 2022 Avi Lumelsky
+"""
+import importlib
+import importlib.machinery # important for importlib to work for our use case
+import os
+import stat
+import time
+from pathlib import Path
+from typing import List
+
+
+from secimport.backends.common.instrumentation_backend import InstrumentationBackend
+from secimport.backends.common.utils import (
+ BASE_DIR_NAME,
+ SECIMPORT_ROOT,
+ render_syscalls_filter,
+)
+
+TEMPLATES_DIR_NAME = SECIMPORT_ROOT / "templates" / "dtrace"
+DEFAULT_BACKEND = InstrumentationBackend.DTRACE
+PROFILES_DIR_NAME = SECIMPORT_ROOT / "profiles"
+
+
+def run_dtrace_script_for_module(
+ module_name: str,
+ allow_shells: bool,
+ allow_networking: bool,
+ use_sudo: bool,
+ log_python_calls: bool,
+ log_syscalls: bool,
+ log_network: bool,
+ log_file_system: bool,
+ destructive: bool,
+ syscalls_allowlist: List[str],
+ syscalls_blocklist: List[str],
+ templates_dir: Path = TEMPLATES_DIR_NAME,
+):
+ module_file_path = create_dtrace_script_for_module(
+ module_name=module_name,
+ allow_shells=allow_shells,
+ allow_networking=allow_networking,
+ log_python_calls=log_python_calls,
+ log_syscalls=log_syscalls,
+ log_network=log_network,
+ log_file_system=log_file_system,
+ destructive=destructive,
+ syscalls_allowlist=syscalls_allowlist,
+ syscalls_blocklist=syscalls_blocklist,
+ templates_dir=templates_dir,
+ )
+ output_file = BASE_DIR_NAME / f"dtrace_sandbox_{module_name}.log"
+ current_pid = os.getpid()
+ dtrace_command = f'{"sudo " if use_sudo else ""} dtrace -q -s {module_file_path} -p {current_pid} -o {output_file} &2>/dev/null'
+ print("(running dtrace supervisor): ", dtrace_command)
+ st = os.stat(module_file_path)
+ os.chmod(module_file_path, st.st_mode | stat.S_IEXEC)
+ os.system(dtrace_command)
+
+ # TODO: wait for dtrace to start explicitly using an event/fd, not time based - although 2 seconds is more than enough.
+ # TODO: add startup logs for dropped packets without python modules (until the first python enter takes place in the syscalls probe, the python module is null).
+ time.sleep(2)
+ return True
+
+
+def create_dtrace_script_for_module(
+ module_name: str,
+ allow_shells: bool,
+ allow_networking: bool,
+ log_python_calls: bool,
+ log_syscalls: bool,
+ log_network: bool,
+ log_file_system: bool,
+ destructive: bool,
+ syscalls_allowlist: List[str] = None,
+ syscalls_blocklist: List[str] = None,
+ templates_dir: Path = TEMPLATES_DIR_NAME,
+) -> str:
+ """
+ # Template components available at the moment:
+ # ###DESTRUCTIVE###
+ # ###FUNCTION_ENTRY###
+ # ###FUNCTION_EXIT###
+ # ###SYSCALL_ENTRY###
+ # ###MODULE_NAME###
+ """
+
+ module = importlib.machinery.PathFinder().find_spec(module_name)
+ if module is None:
+ raise ModuleNotFoundError(module)
+ module_traced_name = module.origin # e.g this.py
+
+ assert not (
+ syscalls_allowlist is not None and syscalls_blocklist is not None
+ ), "Please specify either syscalls_allowlist OR syscalls_blocklist."
+
+ # If we have an allowlist
+ if syscalls_allowlist is not None:
+ script_template = render_allowlist_template(
+ syscalls_allowlist=syscalls_allowlist,
+ templates_dir=templates_dir,
+ )
+ elif syscalls_blocklist is not None:
+ script_template = render_blocklist_template(
+ syscalls_blocklist=syscalls_blocklist, templates_dir=templates_dir
+ )
+ else:
+ script_template = render_dscript_template(
+ module_traced_name=module_traced_name,
+ allow_shells=allow_shells,
+ allow_networking=allow_networking,
+ log_python_calls=log_python_calls,
+ log_syscalls=log_syscalls,
+ log_network=log_network,
+ log_file_system=log_file_system,
+ destructive=destructive,
+ templates_dir=templates_dir,
+ )
+
+ # Creating a dscript file with the modified template
+ if not os.path.exists(BASE_DIR_NAME):
+ os.mkdir(BASE_DIR_NAME)
+
+ module_file_name = os.path.join(BASE_DIR_NAME, f"dtrace_sandbox_{module_name}.d")
+ with open(module_file_name, "w") as module_file:
+ module_file.write(script_template)
+
+ # Making sure the script compiles
+ dtrace_compile_command = f"dtrace -s {module_file_name} -e"
+ compile_exit_code = os.system(dtrace_compile_command)
+ assert (
+ compile_exit_code == 0
+ ), f"Failed to compile the dtrace script at {module_file_name}"
+ print("Successfully compiled dtrace profile: ", module_file_name)
+ return module_file_name
+
+
+def render_allowlist_template(
+ syscalls_allowlist: List[str], templates_dir: Path = TEMPLATES_DIR_NAME
+):
+ print("Found syscall allowlist, ignoring other configurations")
+ script_template = open(
+ templates_dir / "default.allowlist.template.d",
+ "r",
+ ).read()
+ syscalls_filter = render_syscalls_filter(
+ syscalls_list=syscalls_allowlist,
+ allow=True,
+ instrumentation_backend=InstrumentationBackend.DTRACE,
+ )
+ script_template = script_template.replace("###SYSCALL_FILTER###", syscalls_filter)
+ return script_template
+
+
+def render_blocklist_template(
+ syscalls_blocklist: List[str], templates_dir: Path = TEMPLATES_DIR_NAME
+):
+ print("Found syscall allowlist, ignoring other configurations")
+ script_template = open(
+ templates_dir / "default.allowlist.template.d",
+ "r",
+ ).read()
+ syscalls_filter = render_syscalls_filter(
+ syscalls_list=syscalls_blocklist,
+ allow=False,
+ instrumentation_backend=InstrumentationBackend.DTRACE,
+ )
+ script_template = script_template.replace("###SYSCALL_FILTER###", syscalls_filter)
+ return script_template
+
+
+def render_dscript_template(
+ module_traced_name: str,
+ allow_shells: bool,
+ allow_networking: bool,
+ log_python_calls: bool,
+ log_syscalls: bool,
+ log_network: bool,
+ log_file_system: bool,
+ destructive: bool,
+ templates_dir: Path = TEMPLATES_DIR_NAME,
+ default_template_filename: str = "default.template.d",
+):
+ script_template = open(
+ templates_dir / default_template_filename,
+ "r",
+ ).read()
+
+ if destructive is True:
+ destructive = open(templates_dir / "headers/destructive.d", "r").read()
+ script_template = script_template.replace("###DESTRUCTIVE###", destructive)
+ else:
+ script_template = script_template.replace("###DESTRUCTIVE###", "")
+
+ # PYTHON instrumentations
+ code_syscall_entry = ""
+ if log_python_calls is True:
+ code_function_entry = open(
+ templates_dir / "actions/log_python_module_entry.d",
+ "r",
+ ).read()
+ code_function_exit = open(
+ templates_dir / "actions/log_python_module_exit.d",
+ "r",
+ ).read()
+ script_template = script_template.replace(
+ "###FUNCTION_ENTRY###", code_function_entry
+ )
+ script_template = script_template.replace(
+ "###FUNCTION_EXIT###", code_function_exit
+ )
+ else:
+ script_template = script_template.replace("###FUNCTION_ENTRY###", "")
+ script_template = script_template.replace("###FUNCTION_EXIT###", "")
+
+ # SYSCALLS instrumentations
+ if log_syscalls is True:
+ _code = open(
+ templates_dir / "actions/log_syscall.d",
+ "r",
+ ).read()
+ code_syscall_entry += f"{_code};\n"
+
+ if log_file_system is True:
+ filter_fs_code = open(
+ templates_dir / "filters/file_system.d",
+ "r",
+ ).read()
+ code_syscall_entry += f"{filter_fs_code}\n{{"
+ action_log_file_system = open(
+ templates_dir / "actions/log_file_system.d",
+ "r",
+ ).read()
+ code_syscall_entry += f"{action_log_file_system}}}\n"
+
+ if allow_networking is False or log_network is True:
+ filter_networking_code = open(
+ templates_dir / "filters/networking.d",
+ "r",
+ ).read()
+ code_syscall_entry += f"{filter_networking_code}{{\n"
+
+ if log_network is True:
+ action_log_network = open(
+ templates_dir / "actions/log_network.d",
+ "r",
+ ).read()
+ code_syscall_entry += f"{action_log_network}\n"
+
+ if allow_networking is False:
+ action_kill = open(
+ templates_dir / "actions/kill_process.d",
+ "r",
+ ).read()
+ code_syscall_entry += f"{action_kill}\n"
+ code_syscall_entry += "}\n"
+
+ if allow_shells is False:
+ filter_processes_code = open(
+ templates_dir / "filters/processes.d",
+ "r",
+ ).read()
+ code_syscall_entry += f"{filter_processes_code}{{\n"
+ action_kill_on_processing = open(
+ templates_dir / "actions/kill_on_processing.d",
+ "r",
+ ).read()
+ code_syscall_entry += f"{action_kill_on_processing}}}\n"
+
+ script_template = script_template.replace("###SYSCALL_ENTRY###", code_syscall_entry)
+ script_template = script_template.replace("###MODULE_NAME###", module_traced_name)
+ return script_template
+
+
+def _render_probe_for_module(
+ module_name: str,
+ destructive: bool,
+ syscalls_allowlist: List[str],
+ templates_dir: Path = TEMPLATES_DIR_NAME,
+) -> str:
+ # Loading the probe allowlist template
+ probe_template = open(
+ templates_dir / "probes/module_syscalls_allowlist_template.d",
+ "r",
+ ).read()
+
+ # Adding a syscalls filter
+ syscalls_filter = render_syscalls_filter(
+ syscalls_list=syscalls_allowlist,
+ allow=True,
+ instrumentation_backend=InstrumentationBackend.DTRACE,
+ )
+ probe_template = probe_template.replace("###SYSCALL_FILTER###", syscalls_filter)
+
+ # Adding a probe filter for the specified python module
+ supervision_filter = open(
+ templates_dir / "filters/is_current_module_under_supervision.d",
+ "r",
+ ).read()
+
+ # Adding the action according to the 'destructive' flag: kill if destructive, log otherwise
+ supervision_action = ""
+ if destructive is True:
+ action_kill_process = open(
+ templates_dir / "actions/kill_process.d",
+ "r",
+ ).read()
+ supervision_action = f"{{{action_kill_process}}}\n"
+ else:
+ action_log_syscall = open(
+ templates_dir / "actions/log_syscall.d",
+ "r",
+ ).read()
+ supervision_action = f"{{{action_log_syscall}}}\n"
+
+ # Updating the template
+ probe_template = probe_template.replace(
+ "###SUPERVISED_MODULES_FILTER###", supervision_filter
+ )
+ probe_template = probe_template.replace(
+ "###SUPERVISED_MODULES_ACTION###", supervision_action
+ )
+ probe_template = probe_template.replace("###MODULE_NAME###", module_name)
+ return probe_template
+
+
+def build_module_sandbox_from_yaml_template(
+ template_path: Path, templates_dir: Path = TEMPLATES_DIR_NAME
+):
+ """Generated dscript sandbox code for secure imports based on a YAML file.
+
+ Args:
+ template_path (Path): The path to the YAML file, describing the policies.
+ templates_dir (Path, optional): The directory of the templates. Defaults to TEMPLATES_DIR_NAME.
+
+ Raises:
+ ModuleNotFoundError: _description_
+
+ Returns:
+ _type_: _description_
+ """
+ assert template_path.exists(), f"The template does not exist at {template_path}"
+ import yaml
+
+ safe_yaml = yaml.safe_load(open(template_path, "r").read())
+ parsed_probes = []
+ for module_name, module_config in safe_yaml.get("modules", {}).items():
+ # Finding the module without loading
+ module = importlib.machinery.PathFinder().find_spec(module_name)
+ if module is None:
+ raise ModuleNotFoundError(module)
+
+ # Tracing module entrypoint
+ module_traced_name = module.origin
+ # module_traced_name = os.path.split(module_traced_name)[:-1][0]
+
+ _destructive = module_config.get("destructive")
+ assert isinstance(_destructive, bool), ValueError(
+ f'The "destructive" field for module {module_name} is empty.'
+ )
+
+ _syscall_allowlist = module_config.get("syscall_allowlist")
+ assert _syscall_allowlist, ValueError(
+ f'The "syscall_allowlist" for module {module_name} is empty.'
+ )
+ for _ in _syscall_allowlist:
+ assert isinstance(_, str), ValueError(
+ f'The "syscall_allowlist" field for module {module_name} contains invalid string: {_}'
+ )
+
+ module_sandbox_probe = _render_probe_for_module(
+ module_name=module_traced_name,
+ destructive=_destructive,
+ syscalls_allowlist=_syscall_allowlist,
+ )
+ assert module_sandbox_probe, ValueError(
+ f"Failed to create a probe for module {module_name}"
+ )
+ parsed_probes.append(module_sandbox_probe)
+
+ if not parsed_probes:
+ print(f"The profile does not contain any modules: {template_path}")
+ return
+
+ ###SUPERVISED_MODULES_PROBES###
+ script_template = open(
+ templates_dir / "default.yaml.template.d",
+ "r",
+ ).read()
+
+ probes_code = ("\n" * 2).join(parsed_probes)
+ script_template = script_template.replace(
+ "###SUPERVISED_MODULES_PROBES###", probes_code
+ )
+ return script_template
diff --git a/src/secimport/sandbox_helper.py b/src/secimport/sandbox_helper.py
index 8e2466a..313300b 100644
--- a/src/secimport/sandbox_helper.py
+++ b/src/secimport/sandbox_helper.py
@@ -1,35 +1,33 @@
-"""Import python modules with dtrace supervision.
+"""
+Import python modules with syscalls supervision.
Copyright (c) 2022 Avi Lumelsky
"""
-
-import importlib
-import os
-import time
-from pathlib import Path
from typing import List
-
-import yaml
-
-BASE_DIR_NAME = Path("/tmp/.secimport")
-SECIMPORT_ROOT = Path(os.path.split(__file__)[:-1][0])
-TEMPLATES_DIR_NAME = SECIMPORT_ROOT / "templates"
-PROFILES_DIR_NAME = SECIMPORT_ROOT / "profiles"
+from secimport.backends.common.instrumentation_backend import InstrumentationBackend
+from secimport.backends.common.utils import DEFAULT_BACKEND
+from secimport.backends.bpftrace_backend.bpftrace_backend import (
+ run_bpftrace_script_for_module,
+)
+from secimport.backends.dtrace_backend.dtrace_backend import (
+ run_dtrace_script_for_module,
+)
def secure_import(
module_name: str,
allow_shells: bool = False,
allow_networking: bool = False,
- use_sudo: bool = True,
+ use_sudo: bool = False,
log_python_calls: bool = False, # When True, the log file might reach GB in seconds.
- log_syscalls: bool = False, # When True, the log file might reach GB in seconds.
- log_network: bool = False, # When True, the log file might reach GB in seconds.
- log_file_system: bool = False, # When True, the log file might reach GB in seconds.
+ log_syscalls: bool = False,
+ log_network: bool = False,
+ log_file_system: bool = False,
destructive: bool = True,
syscalls_allowlist: List[str] = None,
syscalls_blocklist: List[str] = None,
+ backend=DEFAULT_BACKEND,
):
"""Import a python module in confined settings.
@@ -47,396 +45,37 @@ def secure_import(
Returns:
_type_: A Python Module. The module is supervised by a dtrace process with destructive capabilities unless the 'destructive' argument is set to False.
"""
- assert run_dtrace_script_for_module(
- module_name=module_name,
- allow_shells=allow_shells,
- allow_networking=allow_networking,
- use_sudo=use_sudo,
- log_python_calls=log_python_calls,
- log_syscalls=log_syscalls,
- log_network=log_network,
- log_file_system=log_file_system,
- destructive=destructive,
- syscalls_allowlist=syscalls_allowlist,
- syscalls_blocklist=syscalls_blocklist,
- )
- _module = __import__(module_name)
- return _module
-
-
-def run_dtrace_script_for_module(
- module_name: str,
- allow_shells: bool,
- allow_networking: bool,
- use_sudo: bool,
- log_python_calls: bool,
- log_syscalls: bool,
- log_network: bool,
- log_file_system: bool,
- destructive: bool,
- syscalls_allowlist: List[str],
- syscalls_blocklist: List[str],
- templates_dir: Path = TEMPLATES_DIR_NAME,
-):
- module_file_path = create_dtrace_script_for_module(
- module_name=module_name,
- allow_shells=allow_shells,
- allow_networking=allow_networking,
- log_python_calls=log_python_calls,
- log_syscalls=log_syscalls,
- log_network=log_network,
- log_file_system=log_file_system,
- destructive=destructive,
- syscalls_allowlist=syscalls_allowlist,
- syscalls_blocklist=syscalls_blocklist,
- templates_dir=templates_dir,
- )
- output_file = BASE_DIR_NAME / f"sandbox_{module_name}.log"
- current_pid = os.getpid()
- dtrace_command = f'{"sudo " if use_sudo else ""} dtrace -q -s {module_file_path} -p {current_pid} -o {output_file} &2>/dev/null'
- print("(running dtrace supervisor): ", dtrace_command)
- os.system(dtrace_command)
-
- # TODO: wait for dtrace to start explicitly using an event/fd, not time based - although 2 seconds is more than enough.
- # TODO: add startup logs for dropped packets without python modules (until the first python enter takes place in the syscalls probe, the python module is null).
- time.sleep(2)
- return True
-
-def create_dtrace_script_for_module(
- module_name: str,
- allow_shells: bool,
- allow_networking: bool,
- log_python_calls: bool,
- log_syscalls: bool,
- log_network: bool,
- log_file_system: bool,
- destructive: bool,
- syscalls_allowlist: List[str] = None,
- syscalls_blocklist: List[str] = None,
- templates_dir: Path = TEMPLATES_DIR_NAME,
-) -> str:
- """
- # Template components available at the moment:
- # ###DESTRUCTIVE###
- # ###FUNCTION_ENTRY###
- # ###FUNCTION_EXIT###
- # ###SYSCALL_ENTRY###
- # ###MODULE_NAME###
- """
-
- module = importlib.machinery.PathFinder().find_spec(module_name)
- if module is None:
- raise ModuleNotFoundError(module)
- module_traced_name = module.origin # e.g this.py
-
- assert not (
- syscalls_allowlist is not None and syscalls_blocklist is not None
- ), "Please specify either syscalls_allowlist OR syscalls_blocklist."
-
- # If we have an allowlist
- if syscalls_allowlist is not None:
- script_template = render_allowlist_template(
- syscalls_allowlist=syscalls_allowlist, templates_dir=templates_dir
- )
- elif syscalls_blocklist is not None:
- script_template = render_blocklist_template(
- syscalls_blocklist=syscalls_blocklist, templates_dir=templates_dir
- )
- else:
- script_template = render_dscript_template(
- module_traced_name=module_traced_name,
+ if backend == InstrumentationBackend.EBPF:
+ assert run_bpftrace_script_for_module(
+ module_name=module_name,
allow_shells=allow_shells,
allow_networking=allow_networking,
+ use_sudo=use_sudo,
log_python_calls=log_python_calls,
log_syscalls=log_syscalls,
log_network=log_network,
log_file_system=log_file_system,
destructive=destructive,
- templates_dir=templates_dir,
+ syscalls_allowlist=syscalls_allowlist,
+ syscalls_blocklist=syscalls_blocklist,
)
-
- # Creating a dscript file with the modified template
- if not os.path.exists(BASE_DIR_NAME):
- os.mkdir(BASE_DIR_NAME)
-
- module_file_name = os.path.join(BASE_DIR_NAME, f"sandbox_{module_name}.d")
- with open(module_file_name, "w") as module_file:
- module_file.write(script_template)
-
- # Making sure the script compiles
- dtrace_compile_command = f"dtrace -s {module_file_name} -e"
- compile_exit_code = os.system(dtrace_compile_command)
- assert (
- compile_exit_code == 0
- ), f"Failed to compile the dtrace script at {module_file_name}"
- print("Successfully compiled dtrace profile: ", module_file_name)
- return module_file_name
-
-
-def render_allowlist_template(
- syscalls_allowlist: List[str], templates_dir: Path = TEMPLATES_DIR_NAME
-):
- print("Found syscall allowlist, ignoring other configurations")
- script_template = open(
- templates_dir / "default.allowlist.template.d",
- "r",
- ).read()
- syscalls_filter = render_syscalls_filter(
- syscalls_list=syscalls_allowlist, allow=True
- )
- script_template = script_template.replace("###SYSCALL_FILTER###", syscalls_filter)
- return script_template
-
-
-def render_blocklist_template(
- syscalls_blocklist: List[str], templates_dir: Path = TEMPLATES_DIR_NAME
-):
- print("Found syscall allowlist, ignoring other configurations")
- script_template = open(
- templates_dir / "default.allowlist.template.d",
- "r",
- ).read()
- syscalls_filter = render_syscalls_filter(
- syscalls_list=syscalls_blocklist, allow=False
- )
- script_template = script_template.replace("###SYSCALL_FILTER###", syscalls_filter)
- return script_template
-
-
-def render_dscript_template(
- module_traced_name: str,
- allow_shells: bool,
- allow_networking: bool,
- log_python_calls: bool,
- log_syscalls: bool,
- log_network: bool,
- log_file_system: bool,
- destructive: bool,
- templates_dir: Path = TEMPLATES_DIR_NAME,
- default_template_filename: str = "default.template.d",
-):
- script_template = open(
- templates_dir / default_template_filename,
- "r",
- ).read()
-
- if destructive is True:
- destructive = open(templates_dir / "headers/destructive.d", "r").read()
- script_template = script_template.replace("###DESTRUCTIVE###", destructive)
- else:
- script_template = script_template.replace("###DESTRUCTIVE###", "")
-
- # PYTHON instrumentations
- code_syscall_entry = ""
- if log_python_calls is True:
- code_function_entry = open(
- templates_dir / "actions/log_python_module_entry.d",
- "r",
- ).read()
- code_function_exit = open(
- templates_dir / "actions/log_python_module_exit.d",
- "r",
- ).read()
- script_template = script_template.replace(
- "###FUNCTION_ENTRY###", code_function_entry
- )
- script_template = script_template.replace(
- "###FUNCTION_EXIT###", code_function_exit
+ elif backend == InstrumentationBackend.DTRACE:
+ assert run_dtrace_script_for_module(
+ module_name=module_name,
+ allow_shells=allow_shells,
+ allow_networking=allow_networking,
+ use_sudo=use_sudo,
+ log_python_calls=log_python_calls,
+ log_syscalls=log_syscalls,
+ log_network=log_network,
+ log_file_system=log_file_system,
+ destructive=destructive,
+ syscalls_allowlist=syscalls_allowlist,
+ syscalls_blocklist=syscalls_blocklist,
)
else:
- script_template = script_template.replace("###FUNCTION_ENTRY###", "")
- script_template = script_template.replace("###FUNCTION_EXIT###", "")
-
- # SYSCALLS instrumentations
- if log_syscalls is True:
- _code = open(
- templates_dir / "actions/log_syscall.d",
- "r",
- ).read()
- code_syscall_entry += f"{_code};\n"
-
- if log_file_system is True:
- filter_fs_code = open(
- templates_dir / "filters/file_system.d",
- "r",
- ).read()
- code_syscall_entry += f"{filter_fs_code}\n{{"
- action_log_file_system = open(
- templates_dir / "actions/log_file_system.d",
- "r",
- ).read()
- code_syscall_entry += f"{action_log_file_system}}}\n"
-
- if allow_networking is False or log_network is True:
- filter_networking_code = open(
- templates_dir / "filters/networking.d",
- "r",
- ).read()
- code_syscall_entry += f"{filter_networking_code}{{\n"
-
- if log_network is True:
- action_log_network = open(
- templates_dir / "actions/log_network.d",
- "r",
- ).read()
- code_syscall_entry += f"{action_log_network}\n"
-
- if allow_networking is False:
- action_kill = open(
- templates_dir / "actions/kill_process.d",
- "r",
- ).read()
- code_syscall_entry += f"{action_kill}\n"
- code_syscall_entry += "}\n"
-
- if allow_shells is False:
- filter_processes_code = open(
- templates_dir / "filters/processes.d",
- "r",
- ).read()
- code_syscall_entry += f"{filter_processes_code}{{\n"
- action_kill_on_processing = open(
- templates_dir / "actions/kill_on_processing.d",
- "r",
- ).read()
- code_syscall_entry += f"{action_kill_on_processing}}}\n"
-
- script_template = script_template.replace("###SYSCALL_ENTRY###", code_syscall_entry)
- script_template = script_template.replace("###MODULE_NAME###", module_traced_name)
- return script_template
-
-
-def render_syscalls_filter(syscalls_list: List[str], allow: bool):
- assert isinstance(allow, bool), '"allow" must be a bool value'
- # "=="" means the syscall matches (blocklist), while "!="" means allow only the following.
- match_sign = "!=" if allow else "=="
- syscalls_filter = ""
- for i, _syscall in enumerate(syscalls_list):
- if i > 0:
- syscalls_filter += " && "
- assert isinstance(
- _syscall, str
- ), f"The provided syscall it not a syscall string name: {_syscall}"
- syscalls_filter += f'probefunc {match_sign} "{_syscall}"'
- print(f"Adding syscall {_syscall} to allowlist")
- return syscalls_filter
-
-
-def _render_probe_for_module(
- module_name: str,
- destructive: bool,
- syscalls_allowlist: List[str],
- templates_dir: Path = TEMPLATES_DIR_NAME,
-) -> str:
- # Loading the probe allowlist template
- probe_template = open(
- templates_dir / "probes/module_syscalls_allowlist_template.d",
- "r",
- ).read()
-
- # Adding a syscalls filter
- syscalls_filter = render_syscalls_filter(
- syscalls_list=syscalls_allowlist, allow=True
- )
- probe_template = probe_template.replace("###SYSCALL_FILTER###", syscalls_filter)
-
- # Adding a probe filter for the specified python module
- supervision_filter = open(
- templates_dir / "filters/is_current_module_under_supervision.d",
- "r",
- ).read()
-
- # Adding the action according to the 'destructive' flag: kill if destructive, log otherwise
- supervision_action = ""
- if destructive is True:
- action_kill_process = open(
- templates_dir / "actions/kill_process.d",
- "r",
- ).read()
- supervision_action = f"{{{action_kill_process}}}\n"
- else:
- action_log_syscall = open(
- templates_dir / "actions/log_syscall.d",
- "r",
- ).read()
- supervision_action = f"{{{action_log_syscall}}}\n"
-
- # Updating the template
- probe_template = probe_template.replace(
- "###SUPERVISED_MODULES_FILTER###", supervision_filter
- )
- probe_template = probe_template.replace(
- "###SUPERVISED_MODULES_ACTION###", supervision_action
- )
- probe_template = probe_template.replace("###MODULE_NAME###", module_name)
- return probe_template
+ raise NotImplementedError(f"backend '{backend}' is not implemented.")
-
-def build_module_sandbox_from_yaml_template(
- template_path: Path, templates_dir: Path = TEMPLATES_DIR_NAME
-):
- """Generated dscript sandbox code for secure imports based on a YAML file.
-
- Args:
- template_path (Path): The path to the YAML file, describing the policies.
- templates_dir (Path, optional): The directory of the templates. Defaults to TEMPLATES_DIR_NAME.
-
- Raises:
- ModuleNotFoundError: _description_
-
- Returns:
- _type_: _description_
- """
- assert template_path.exists(), f"The template does not exist at {template_path}"
- safe_yaml = yaml.safe_load(open(template_path, "r").read())
- parsed_probes = []
- for module_name, module_config in safe_yaml.get("modules", {}).items():
- # Finding the module without loading
- module = importlib.machinery.PathFinder().find_spec(module_name)
- if module is None:
- raise ModuleNotFoundError(module)
-
- # Tracing module entrypoint
- module_traced_name = module.origin
- # module_traced_name = os.path.split(module_traced_name)[:-1][0]
-
- _destructive = module_config.get("destructive")
- assert isinstance(_destructive, bool), ValueError(
- f'The "destructive" field for module {module_name} is empty.'
- )
-
- _syscall_allowlist = module_config.get("syscall_allowlist")
- assert _syscall_allowlist, ValueError(
- f'The "syscall_allowlist" for module {module_name} is empty.'
- )
- for _ in _syscall_allowlist:
- assert isinstance(_, str), ValueError(
- f'The "syscall_allowlist" field for module {module_name} contains invalid string: {_}'
- )
-
- module_sandbox_probe = _render_probe_for_module(
- module_name=module_traced_name,
- destructive=_destructive,
- syscalls_allowlist=_syscall_allowlist,
- )
- assert module_sandbox_probe, ValueError(
- f"Failed to create a probe for module {module_name}"
- )
- parsed_probes.append(module_sandbox_probe)
-
- if not parsed_probes:
- print(f"The profile does not contain any modules: {template_path}")
- return
-
- ###SUPERVISED_MODULES_PROBES###
- script_template = open(
- templates_dir / "default.yaml.template.d",
- "r",
- ).read()
-
- probes_code = ("\n" * 2).join(parsed_probes)
- script_template = script_template.replace(
- "###SUPERVISED_MODULES_PROBES###", probes_code
- )
- return script_template
+ _module = __import__(module_name)
+ return _module
diff --git a/src/secimport/templates/bpftrace/actions/kill_on_processing.bt b/src/secimport/templates/bpftrace/actions/kill_on_processing.bt
new file mode 100644
index 0000000..539ae5a
--- /dev/null
+++ b/src/secimport/templates/bpftrace/actions/kill_on_processing.bt
@@ -0,0 +1,7 @@
+ if (###DESTRUCTIVE###){
+ printf("\nKILLING PROCESS %s - EXECUTED execve;\n", str(pid));
+ printf("\t\tKILLING...\r\n");
+ system("pkill -9 python"); // optional
+ printf("\t\tKILLED.\r\n");
+ exit(); // optional
+ }
diff --git a/src/secimport/templates/bpftrace/actions/kill_process.bt b/src/secimport/templates/bpftrace/actions/kill_process.bt
new file mode 100644
index 0000000..b03d0f6
--- /dev/null
+++ b/src/secimport/templates/bpftrace/actions/kill_process.bt
@@ -0,0 +1,5 @@
+ printf("\nKILLING PROCESS %s - EXECUTED execve;\n", str(pid));
+ printf("\t\tKILLING...\r\n");
+ system("pkill -9 python"); // optional
+ printf("\t\tKILLED.\r\n");
+ exit(); // optional
\ No newline at end of file
diff --git a/src/secimport/templates/bpftrace/actions/log_file_system.bt b/src/secimport/templates/bpftrace/actions/log_file_system.bt
new file mode 100644
index 0000000..b293f2c
--- /dev/null
+++ b/src/secimport/templates/bpftrace/actions/log_file_system.bt
@@ -0,0 +1 @@
+printf("\t\t(TOUCHING FILESYSTEM): %s(%d) in python module %s\r\n", @sysname[args->id], arg1, @globals["current_module"]);
\ No newline at end of file
diff --git a/src/secimport/templates/bpftrace/actions/log_network.bt b/src/secimport/templates/bpftrace/actions/log_network.bt
new file mode 100644
index 0000000..171b94f
--- /dev/null
+++ b/src/secimport/templates/bpftrace/actions/log_network.bt
@@ -0,0 +1 @@
+printf("\t\t(NETWORKING): %s in python module %s\r\n", @sysname[args->id], arg1, @globals["current_module"]);
\ No newline at end of file
diff --git a/src/secimport/templates/bpftrace/actions/log_python_module_entry.bt b/src/secimport/templates/bpftrace/actions/log_python_module_entry.bt
new file mode 100644
index 0000000..a52c71f
--- /dev/null
+++ b/src/secimport/templates/bpftrace/actions/log_python_module_entry.bt
@@ -0,0 +1 @@
+printf("%s, %s, depth=%d\n", str(arg0), str(arg1), @["depth"]) ;
\ No newline at end of file
diff --git a/src/secimport/templates/bpftrace/actions/log_python_module_exit.bt b/src/secimport/templates/bpftrace/actions/log_python_module_exit.bt
new file mode 100644
index 0000000..e69de29
diff --git a/src/secimport/templates/bpftrace/actions/log_syscall.bt b/src/secimport/templates/bpftrace/actions/log_syscall.bt
new file mode 100644
index 0000000..08857e0
--- /dev/null
+++ b/src/secimport/templates/bpftrace/actions/log_syscall.bt
@@ -0,0 +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"] );
\ No newline at end of file
diff --git a/src/secimport/templates/bpftrace/default.template.bt b/src/secimport/templates/bpftrace/default.template.bt
new file mode 100644
index 0000000..2993c6d
--- /dev/null
+++ b/src/secimport/templates/bpftrace/default.template.bt
@@ -0,0 +1,708 @@
+#!/usr/bin/env bpftrace
+
+BEGIN {
+ // Mapping all syscalls both ways, based on https://github.com/iovisor/bpftrace/blob/2c7a7a598dbe1aa790db2dfe2db242aa69137d5b/tools/syscount.bt
+ // Generates using bash:
+ // $ apt-get install auditd
+ // $ ausyscall --dump | awk 'NR > 1 { printf("\t@sysname[%d] = \"%s\";\n", $1, $2); }';
+ printf("REGISTERING SYSCALLS...\n");
+ @sysname[0] = "read";
+ @sysname[1] = "write";
+ @sysname[2] = "open";
+ @sysname[3] = "close";
+ @sysname[4] = "stat";
+ @sysname[5] = "fstat";
+ @sysname[6] = "lstat";
+ @sysname[7] = "poll";
+ @sysname[8] = "lseek";
+ @sysname[9] = "mmap";
+ @sysname[10] = "mprotect";
+ @sysname[11] = "munmap";
+ @sysname[12] = "brk";
+ @sysname[13] = "rt_sigaction";
+ @sysname[14] = "rt_sigprocmask";
+ @sysname[15] = "rt_sigreturn";
+ @sysname[16] = "ioctl";
+ @sysname[17] = "pread";
+ @sysname[18] = "pwrite";
+ @sysname[19] = "readv";
+ @sysname[20] = "writev";
+ @sysname[21] = "access";
+ @sysname[22] = "pipe";
+ @sysname[23] = "select";
+ @sysname[24] = "sched_yield";
+ @sysname[25] = "mremap";
+ @sysname[26] = "msync";
+ @sysname[27] = "mincore";
+ @sysname[28] = "madvise";
+ @sysname[29] = "shmget";
+ @sysname[30] = "shmat";
+ @sysname[31] = "shmctl";
+ @sysname[32] = "dup";
+ @sysname[33] = "dup2";
+ @sysname[34] = "pause";
+ @sysname[35] = "nanosleep";
+ @sysname[36] = "getitimer";
+ @sysname[37] = "alarm";
+ @sysname[38] = "setitimer";
+ @sysname[39] = "getpid";
+ @sysname[40] = "sendfile";
+ @sysname[41] = "socket";
+ @sysname[42] = "connect";
+ @sysname[43] = "accept";
+ @sysname[44] = "sendto";
+ @sysname[45] = "recvfrom";
+ @sysname[46] = "sendmsg";
+ @sysname[47] = "recvmsg";
+ @sysname[48] = "shutdown";
+ @sysname[49] = "bind";
+ @sysname[50] = "listen";
+ @sysname[51] = "getsockname";
+ @sysname[52] = "getpeername";
+ @sysname[53] = "socketpair";
+ @sysname[54] = "setsockopt";
+ @sysname[55] = "getsockopt";
+ @sysname[56] = "clone";
+ @sysname[57] = "fork";
+ @sysname[58] = "vfork";
+ @sysname[59] = "execve";
+ @sysname[60] = "exit";
+ @sysname[61] = "wait4";
+ @sysname[62] = "kill";
+ @sysname[63] = "uname";
+ @sysname[64] = "semget";
+ @sysname[65] = "semop";
+ @sysname[66] = "semctl";
+ @sysname[67] = "shmdt";
+ @sysname[68] = "msgget";
+ @sysname[69] = "msgsnd";
+ @sysname[70] = "msgrcv";
+ @sysname[71] = "msgctl";
+ @sysname[72] = "fcntl";
+ @sysname[73] = "flock";
+ @sysname[74] = "fsync";
+ @sysname[75] = "fdatasync";
+ @sysname[76] = "truncate";
+ @sysname[77] = "ftruncate";
+ @sysname[78] = "getdents";
+ @sysname[79] = "getcwd";
+ @sysname[80] = "chdir";
+ @sysname[81] = "fchdir";
+ @sysname[82] = "rename";
+ @sysname[83] = "mkdir";
+ @sysname[84] = "rmdir";
+ @sysname[85] = "creat";
+ @sysname[86] = "link";
+ @sysname[87] = "unlink";
+ @sysname[88] = "symlink";
+ @sysname[89] = "readlink";
+ @sysname[90] = "chmod";
+ @sysname[91] = "fchmod";
+ @sysname[92] = "chown";
+ @sysname[93] = "fchown";
+ @sysname[94] = "lchown";
+ @sysname[95] = "umask";
+ @sysname[96] = "gettimeofday";
+ @sysname[97] = "getrlimit";
+ @sysname[98] = "getrusage";
+ @sysname[99] = "sysinfo";
+ @sysname[100] = "times";
+ @sysname[101] = "ptrace";
+ @sysname[102] = "getuid";
+ @sysname[103] = "syslog";
+ @sysname[104] = "getgid";
+ @sysname[105] = "setuid";
+ @sysname[106] = "setgid";
+ @sysname[107] = "geteuid";
+ @sysname[108] = "getegid";
+ @sysname[109] = "setpgid";
+ @sysname[110] = "getppid";
+ @sysname[111] = "getpgrp";
+ @sysname[112] = "setsid";
+ @sysname[113] = "setreuid";
+ @sysname[114] = "setregid";
+ @sysname[115] = "getgroups";
+ @sysname[116] = "setgroups";
+ @sysname[117] = "setresuid";
+ @sysname[118] = "getresuid";
+ @sysname[119] = "setresgid";
+ @sysname[120] = "getresgid";
+ @sysname[121] = "getpgid";
+ @sysname[122] = "setfsuid";
+ @sysname[123] = "setfsgid";
+ @sysname[124] = "getsid";
+ @sysname[125] = "capget";
+ @sysname[126] = "capset";
+ @sysname[127] = "rt_sigpending";
+ @sysname[128] = "rt_sigtimedwait";
+ @sysname[129] = "rt_sigqueueinfo";
+ @sysname[130] = "rt_sigsuspend";
+ @sysname[131] = "sigaltstack";
+ @sysname[132] = "utime";
+ @sysname[133] = "mknod";
+ @sysname[134] = "uselib";
+ @sysname[135] = "personality";
+ @sysname[136] = "ustat";
+ @sysname[137] = "statfs";
+ @sysname[138] = "fstatfs";
+ @sysname[139] = "sysfs";
+ @sysname[140] = "getpriority";
+ @sysname[141] = "setpriority";
+ @sysname[142] = "sched_setparam";
+ @sysname[143] = "sched_getparam";
+ @sysname[144] = "sched_setscheduler";
+ @sysname[145] = "sched_getscheduler";
+ @sysname[146] = "sched_get_priority_max";
+ @sysname[147] = "sched_get_priority_min";
+ @sysname[148] = "sched_rr_get_interval";
+ @sysname[149] = "mlock";
+ @sysname[150] = "munlock";
+ @sysname[151] = "mlockall";
+ @sysname[152] = "munlockall";
+ @sysname[153] = "vhangup";
+ @sysname[154] = "modify_ldt";
+ @sysname[155] = "pivot_root";
+ @sysname[156] = "_sysctl";
+ @sysname[157] = "prctl";
+ @sysname[158] = "arch_prctl";
+ @sysname[159] = "adjtimex";
+ @sysname[160] = "setrlimit";
+ @sysname[161] = "chroot";
+ @sysname[162] = "sync";
+ @sysname[163] = "acct";
+ @sysname[164] = "settimeofday";
+ @sysname[165] = "mount";
+ @sysname[166] = "umount2";
+ @sysname[167] = "swapon";
+ @sysname[168] = "swapoff";
+ @sysname[169] = "reboot";
+ @sysname[170] = "sethostname";
+ @sysname[171] = "setdomainname";
+ @sysname[172] = "iopl";
+ @sysname[173] = "ioperm";
+ @sysname[174] = "create_module";
+ @sysname[175] = "init_module";
+ @sysname[176] = "delete_module";
+ @sysname[177] = "get_kernel_syms";
+ @sysname[178] = "query_module";
+ @sysname[179] = "quotactl";
+ @sysname[180] = "nfsservctl";
+ @sysname[181] = "getpmsg";
+ @sysname[182] = "putpmsg";
+ @sysname[183] = "afs_syscall";
+ @sysname[184] = "tuxcall";
+ @sysname[185] = "security";
+ @sysname[186] = "gettid";
+ @sysname[187] = "readahead";
+ @sysname[188] = "setxattr";
+ @sysname[189] = "lsetxattr";
+ @sysname[190] = "fsetxattr";
+ @sysname[191] = "getxattr";
+ @sysname[192] = "lgetxattr";
+ @sysname[193] = "fgetxattr";
+ @sysname[194] = "listxattr";
+ @sysname[195] = "llistxattr";
+ @sysname[196] = "flistxattr";
+ @sysname[197] = "removexattr";
+ @sysname[198] = "lremovexattr";
+ @sysname[199] = "fremovexattr";
+ @sysname[200] = "tkill";
+ @sysname[201] = "time";
+ @sysname[202] = "futex";
+ @sysname[203] = "sched_setaffinity";
+ @sysname[204] = "sched_getaffinity";
+ @sysname[205] = "set_thread_area";
+ @sysname[206] = "io_setup";
+ @sysname[207] = "io_destroy";
+ @sysname[208] = "io_getevents";
+ @sysname[209] = "io_submit";
+ @sysname[210] = "io_cancel";
+ @sysname[211] = "get_thread_area";
+ @sysname[212] = "lookup_dcookie";
+ @sysname[213] = "epoll_create";
+ @sysname[214] = "epoll_ctl_old";
+ @sysname[215] = "epoll_wait_old";
+ @sysname[216] = "remap_file_pages";
+ @sysname[217] = "getdents64";
+ @sysname[218] = "set_tid_address";
+ @sysname[219] = "restart_syscall";
+ @sysname[220] = "semtimedop";
+ @sysname[221] = "fadvise64";
+ @sysname[222] = "timer_create";
+ @sysname[223] = "timer_settime";
+ @sysname[224] = "timer_gettime";
+ @sysname[225] = "timer_getoverrun";
+ @sysname[226] = "timer_delete";
+ @sysname[227] = "clock_settime";
+ @sysname[228] = "clock_gettime";
+ @sysname[229] = "clock_getres";
+ @sysname[230] = "clock_nanosleep";
+ @sysname[231] = "exit_group";
+ @sysname[232] = "epoll_wait";
+ @sysname[233] = "epoll_ctl";
+ @sysname[234] = "tgkill";
+ @sysname[235] = "utimes";
+ @sysname[236] = "vserver";
+ @sysname[237] = "mbind";
+ @sysname[238] = "set_mempolicy";
+ @sysname[239] = "get_mempolicy";
+ @sysname[240] = "mq_open";
+ @sysname[241] = "mq_unlink";
+ @sysname[242] = "mq_timedsend";
+ @sysname[243] = "mq_timedreceive";
+ @sysname[244] = "mq_notify";
+ @sysname[245] = "mq_getsetattr";
+ @sysname[246] = "kexec_load";
+ @sysname[247] = "waitid";
+ @sysname[248] = "add_key";
+ @sysname[249] = "request_key";
+ @sysname[250] = "keyctl";
+ @sysname[251] = "ioprio_set";
+ @sysname[252] = "ioprio_get";
+ @sysname[253] = "inotify_init";
+ @sysname[254] = "inotify_add_watch";
+ @sysname[255] = "inotify_rm_watch";
+ @sysname[256] = "migrate_pages";
+ @sysname[257] = "openat";
+ @sysname[258] = "mkdirat";
+ @sysname[259] = "mknodat";
+ @sysname[260] = "fchownat";
+ @sysname[261] = "futimesat";
+ @sysname[262] = "newfstatat";
+ @sysname[263] = "unlinkat";
+ @sysname[264] = "renameat";
+ @sysname[265] = "linkat";
+ @sysname[266] = "symlinkat";
+ @sysname[267] = "readlinkat";
+ @sysname[268] = "fchmodat";
+ @sysname[269] = "faccessat";
+ @sysname[270] = "pselect6";
+ @sysname[271] = "ppoll";
+ @sysname[272] = "unshare";
+ @sysname[273] = "set_robust_list";
+ @sysname[274] = "get_robust_list";
+ @sysname[275] = "splice";
+ @sysname[276] = "tee";
+ @sysname[277] = "sync_file_range";
+ @sysname[278] = "vmsplice";
+ @sysname[279] = "move_pages";
+ @sysname[280] = "utimensat";
+ @sysname[281] = "epoll_pwait";
+ @sysname[282] = "signalfd";
+ @sysname[283] = "timerfd";
+ @sysname[284] = "eventfd";
+ @sysname[285] = "fallocate";
+ @sysname[286] = "timerfd_settime";
+ @sysname[287] = "timerfd_gettime";
+ @sysname[288] = "accept4";
+ @sysname[289] = "signalfd4";
+ @sysname[290] = "eventfd2";
+ @sysname[291] = "epoll_create1";
+ @sysname[292] = "dup3";
+ @sysname[293] = "pipe2";
+ @sysname[294] = "inotify_init1";
+ @sysname[295] = "preadv";
+ @sysname[296] = "pwritev";
+ @sysname[297] = "rt_tgsigqueueinfo";
+ @sysname[298] = "perf_event_open";
+ @sysname[299] = "recvmmsg";
+ @sysname[300] = "fanotify_init";
+ @sysname[301] = "fanotify_mark";
+ @sysname[302] = "prlimit64";
+ @sysname[303] = "name_to_handle_at";
+ @sysname[304] = "open_by_handle_at";
+ @sysname[305] = "clock_adjtime";
+ @sysname[306] = "syncfs";
+ @sysname[307] = "sendmmsg";
+ @sysname[308] = "setns";
+ @sysname[309] = "getcpu";
+ @sysname[310] = "process_vm_readv";
+ @sysname[311] = "process_vm_writev";
+ @sysname[312] = "kcmp";
+ @sysname[313] = "finit_module";
+ @sysname[314] = "sched_setattr";
+ @sysname[315] = "sched_getattr";
+ @sysname[316] = "renameat2";
+ @sysname[317] = "seccomp";
+ @sysname[318] = "getrandom";
+ @sysname[319] = "memfd_create";
+ @sysname[320] = "kexec_file_load";
+ @sysname[321] = "bpf";
+ @sysname[322] = "execveat";
+ @sysname[323] = "userfaultfd";
+ @sysname[324] = "membarrier";
+ @sysname[325] = "mlock2";
+ @sysname[326] = "copy_file_range";
+ @sysname[327] = "preadv2";
+ @sysname[328] = "pwritev2";
+ @sysname[329] = "pkey_mprotect";
+ @sysname[330] = "pkey_alloc";
+ @sysname[331] = "pkey_free";
+ @sysname[332] = "statx";
+ @sysname[333] = "io_pgetevents";
+ @sysname[334] = "rseq";
+
+ // Reverse mapping
+ // ausyscall --dump | awk 'NR > 1 { printf("\t@sysnum[\"%s\"] = %d;\n", $2, $1); }';
+ @sysnum["read"] = 0;
+ @sysnum["write"] = 1;
+ @sysnum["open"] = 2;
+ @sysnum["close"] = 3;
+ @sysnum["stat"] = 4;
+ @sysnum["fstat"] = 5;
+ @sysnum["lstat"] = 6;
+ @sysnum["poll"] = 7;
+ @sysnum["lseek"] = 8;
+ @sysnum["mmap"] = 9;
+ @sysnum["mprotect"] = 10;
+ @sysnum["munmap"] = 11;
+ @sysnum["brk"] = 12;
+ @sysnum["rt_sigaction"] = 13;
+ @sysnum["rt_sigprocmask"] = 14;
+ @sysnum["rt_sigreturn"] = 15;
+ @sysnum["ioctl"] = 16;
+ @sysnum["pread"] = 17;
+ @sysnum["pwrite"] = 18;
+ @sysnum["readv"] = 19;
+ @sysnum["writev"] = 20;
+ @sysnum["access"] = 21;
+ @sysnum["pipe"] = 22;
+ @sysnum["select"] = 23;
+ @sysnum["sched_yield"] = 24;
+ @sysnum["mremap"] = 25;
+ @sysnum["msync"] = 26;
+ @sysnum["mincore"] = 27;
+ @sysnum["madvise"] = 28;
+ @sysnum["shmget"] = 29;
+ @sysnum["shmat"] = 30;
+ @sysnum["shmctl"] = 31;
+ @sysnum["dup"] = 32;
+ @sysnum["dup2"] = 33;
+ @sysnum["pause"] = 34;
+ @sysnum["nanosleep"] = 35;
+ @sysnum["getitimer"] = 36;
+ @sysnum["alarm"] = 37;
+ @sysnum["setitimer"] = 38;
+ @sysnum["getpid"] = 39;
+ @sysnum["sendfile"] = 40;
+ @sysnum["socket"] = 41;
+ @sysnum["connect"] = 42;
+ @sysnum["accept"] = 43;
+ @sysnum["sendto"] = 44;
+ @sysnum["recvfrom"] = 45;
+ @sysnum["sendmsg"] = 46;
+ @sysnum["recvmsg"] = 47;
+ @sysnum["shutdown"] = 48;
+ @sysnum["bind"] = 49;
+ @sysnum["listen"] = 50;
+ @sysnum["getsockname"] = 51;
+ @sysnum["getpeername"] = 52;
+ @sysnum["socketpair"] = 53;
+ @sysnum["setsockopt"] = 54;
+ @sysnum["getsockopt"] = 55;
+ @sysnum["clone"] = 56;
+ @sysnum["fork"] = 57;
+ @sysnum["vfork"] = 58;
+ @sysnum["execve"] = 59;
+ @sysnum["exit"] = 60;
+ @sysnum["wait4"] = 61;
+ @sysnum["kill"] = 62;
+ @sysnum["uname"] = 63;
+ @sysnum["semget"] = 64;
+ @sysnum["semop"] = 65;
+ @sysnum["semctl"] = 66;
+ @sysnum["shmdt"] = 67;
+ @sysnum["msgget"] = 68;
+ @sysnum["msgsnd"] = 69;
+ @sysnum["msgrcv"] = 70;
+ @sysnum["msgctl"] = 71;
+ @sysnum["fcntl"] = 72;
+ @sysnum["flock"] = 73;
+ @sysnum["fsync"] = 74;
+ @sysnum["fdatasync"] = 75;
+ @sysnum["truncate"] = 76;
+ @sysnum["ftruncate"] = 77;
+ @sysnum["getdents"] = 78;
+ @sysnum["getcwd"] = 79;
+ @sysnum["chdir"] = 80;
+ @sysnum["fchdir"] = 81;
+ @sysnum["rename"] = 82;
+ @sysnum["mkdir"] = 83;
+ @sysnum["rmdir"] = 84;
+ @sysnum["creat"] = 85;
+ @sysnum["link"] = 86;
+ @sysnum["unlink"] = 87;
+ @sysnum["symlink"] = 88;
+ @sysnum["readlink"] = 89;
+ @sysnum["chmod"] = 90;
+ @sysnum["fchmod"] = 91;
+ @sysnum["chown"] = 92;
+ @sysnum["fchown"] = 93;
+ @sysnum["lchown"] = 94;
+ @sysnum["umask"] = 95;
+ @sysnum["gettimeofday"] = 96;
+ @sysnum["getrlimit"] = 97;
+ @sysnum["getrusage"] = 98;
+ @sysnum["sysinfo"] = 99;
+ @sysnum["times"] = 100;
+ @sysnum["ptrace"] = 101;
+ @sysnum["getuid"] = 102;
+ @sysnum["syslog"] = 103;
+ @sysnum["getgid"] = 104;
+ @sysnum["setuid"] = 105;
+ @sysnum["setgid"] = 106;
+ @sysnum["geteuid"] = 107;
+ @sysnum["getegid"] = 108;
+ @sysnum["setpgid"] = 109;
+ @sysnum["getppid"] = 110;
+ @sysnum["getpgrp"] = 111;
+ @sysnum["setsid"] = 112;
+ @sysnum["setreuid"] = 113;
+ @sysnum["setregid"] = 114;
+ @sysnum["getgroups"] = 115;
+ @sysnum["setgroups"] = 116;
+ @sysnum["setresuid"] = 117;
+ @sysnum["getresuid"] = 118;
+ @sysnum["setresgid"] = 119;
+ @sysnum["getresgid"] = 120;
+ @sysnum["getpgid"] = 121;
+ @sysnum["setfsuid"] = 122;
+ @sysnum["setfsgid"] = 123;
+ @sysnum["getsid"] = 124;
+ @sysnum["capget"] = 125;
+ @sysnum["capset"] = 126;
+ @sysnum["rt_sigpending"] = 127;
+ @sysnum["rt_sigtimedwait"] = 128;
+ @sysnum["rt_sigqueueinfo"] = 129;
+ @sysnum["rt_sigsuspend"] = 130;
+ @sysnum["sigaltstack"] = 131;
+ @sysnum["utime"] = 132;
+ @sysnum["mknod"] = 133;
+ @sysnum["uselib"] = 134;
+ @sysnum["personality"] = 135;
+ @sysnum["ustat"] = 136;
+ @sysnum["statfs"] = 137;
+ @sysnum["fstatfs"] = 138;
+ @sysnum["sysfs"] = 139;
+ @sysnum["getpriority"] = 140;
+ @sysnum["setpriority"] = 141;
+ @sysnum["sched_setparam"] = 142;
+ @sysnum["sched_getparam"] = 143;
+ @sysnum["sched_setscheduler"] = 144;
+ @sysnum["sched_getscheduler"] = 145;
+ @sysnum["sched_get_priority_max"] = 146;
+ @sysnum["sched_get_priority_min"] = 147;
+ @sysnum["sched_rr_get_interval"] = 148;
+ @sysnum["mlock"] = 149;
+ @sysnum["munlock"] = 150;
+ @sysnum["mlockall"] = 151;
+ @sysnum["munlockall"] = 152;
+ @sysnum["vhangup"] = 153;
+ @sysnum["modify_ldt"] = 154;
+ @sysnum["pivot_root"] = 155;
+ @sysnum["_sysctl"] = 156;
+ @sysnum["prctl"] = 157;
+ @sysnum["arch_prctl"] = 158;
+ @sysnum["adjtimex"] = 159;
+ @sysnum["setrlimit"] = 160;
+ @sysnum["chroot"] = 161;
+ @sysnum["sync"] = 162;
+ @sysnum["acct"] = 163;
+ @sysnum["settimeofday"] = 164;
+ @sysnum["mount"] = 165;
+ @sysnum["umount2"] = 166;
+ @sysnum["swapon"] = 167;
+ @sysnum["swapoff"] = 168;
+ @sysnum["reboot"] = 169;
+ @sysnum["sethostname"] = 170;
+ @sysnum["setdomainname"] = 171;
+ @sysnum["iopl"] = 172;
+ @sysnum["ioperm"] = 173;
+ @sysnum["create_module"] = 174;
+ @sysnum["init_module"] = 175;
+ @sysnum["delete_module"] = 176;
+ @sysnum["get_kernel_syms"] = 177;
+ @sysnum["query_module"] = 178;
+ @sysnum["quotactl"] = 179;
+ @sysnum["nfsservctl"] = 180;
+ @sysnum["getpmsg"] = 181;
+ @sysnum["putpmsg"] = 182;
+ @sysnum["afs_syscall"] = 183;
+ @sysnum["tuxcall"] = 184;
+ @sysnum["security"] = 185;
+ @sysnum["gettid"] = 186;
+ @sysnum["readahead"] = 187;
+ @sysnum["setxattr"] = 188;
+ @sysnum["lsetxattr"] = 189;
+ @sysnum["fsetxattr"] = 190;
+ @sysnum["getxattr"] = 191;
+ @sysnum["lgetxattr"] = 192;
+ @sysnum["fgetxattr"] = 193;
+ @sysnum["listxattr"] = 194;
+ @sysnum["llistxattr"] = 195;
+ @sysnum["flistxattr"] = 196;
+ @sysnum["removexattr"] = 197;
+ @sysnum["lremovexattr"] = 198;
+ @sysnum["fremovexattr"] = 199;
+ @sysnum["tkill"] = 200;
+ @sysnum["time"] = 201;
+ @sysnum["futex"] = 202;
+ @sysnum["sched_setaffinity"] = 203;
+ @sysnum["sched_getaffinity"] = 204;
+ @sysnum["set_thread_area"] = 205;
+ @sysnum["io_setup"] = 206;
+ @sysnum["io_destroy"] = 207;
+ @sysnum["io_getevents"] = 208;
+ @sysnum["io_submit"] = 209;
+ @sysnum["io_cancel"] = 210;
+ @sysnum["get_thread_area"] = 211;
+ @sysnum["lookup_dcookie"] = 212;
+ @sysnum["epoll_create"] = 213;
+ @sysnum["epoll_ctl_old"] = 214;
+ @sysnum["epoll_wait_old"] = 215;
+ @sysnum["remap_file_pages"] = 216;
+ @sysnum["getdents64"] = 217;
+ @sysnum["set_tid_address"] = 218;
+ @sysnum["restart_syscall"] = 219;
+ @sysnum["semtimedop"] = 220;
+ @sysnum["fadvise64"] = 221;
+ @sysnum["timer_create"] = 222;
+ @sysnum["timer_settime"] = 223;
+ @sysnum["timer_gettime"] = 224;
+ @sysnum["timer_getoverrun"] = 225;
+ @sysnum["timer_delete"] = 226;
+ @sysnum["clock_settime"] = 227;
+ @sysnum["clock_gettime"] = 228;
+ @sysnum["clock_getres"] = 229;
+ @sysnum["clock_nanosleep"] = 230;
+ @sysnum["exit_group"] = 231;
+ @sysnum["epoll_wait"] = 232;
+ @sysnum["epoll_ctl"] = 233;
+ @sysnum["tgkill"] = 234;
+ @sysnum["utimes"] = 235;
+ @sysnum["vserver"] = 236;
+ @sysnum["mbind"] = 237;
+ @sysnum["set_mempolicy"] = 238;
+ @sysnum["get_mempolicy"] = 239;
+ @sysnum["mq_open"] = 240;
+ @sysnum["mq_unlink"] = 241;
+ @sysnum["mq_timedsend"] = 242;
+ @sysnum["mq_timedreceive"] = 243;
+ @sysnum["mq_notify"] = 244;
+ @sysnum["mq_getsetattr"] = 245;
+ @sysnum["kexec_load"] = 246;
+ @sysnum["waitid"] = 247;
+ @sysnum["add_key"] = 248;
+ @sysnum["request_key"] = 249;
+ @sysnum["keyctl"] = 250;
+ @sysnum["ioprio_set"] = 251;
+ @sysnum["ioprio_get"] = 252;
+ @sysnum["inotify_init"] = 253;
+ @sysnum["inotify_add_watch"] = 254;
+ @sysnum["inotify_rm_watch"] = 255;
+ @sysnum["migrate_pages"] = 256;
+ @sysnum["openat"] = 257;
+ @sysnum["mkdirat"] = 258;
+ @sysnum["mknodat"] = 259;
+ @sysnum["fchownat"] = 260;
+ @sysnum["futimesat"] = 261;
+ @sysnum["newfstatat"] = 262;
+ @sysnum["unlinkat"] = 263;
+ @sysnum["renameat"] = 264;
+ @sysnum["linkat"] = 265;
+ @sysnum["symlinkat"] = 266;
+ @sysnum["readlinkat"] = 267;
+ @sysnum["fchmodat"] = 268;
+ @sysnum["faccessat"] = 269;
+ @sysnum["pselect6"] = 270;
+ @sysnum["ppoll"] = 271;
+ @sysnum["unshare"] = 272;
+ @sysnum["set_robust_list"] = 273;
+ @sysnum["get_robust_list"] = 274;
+ @sysnum["splice"] = 275;
+ @sysnum["tee"] = 276;
+ @sysnum["sync_file_range"] = 277;
+ @sysnum["vmsplice"] = 278;
+ @sysnum["move_pages"] = 279;
+ @sysnum["utimensat"] = 280;
+ @sysnum["epoll_pwait"] = 281;
+ @sysnum["signalfd"] = 282;
+ @sysnum["timerfd"] = 283;
+ @sysnum["eventfd"] = 284;
+ @sysnum["fallocate"] = 285;
+ @sysnum["timerfd_settime"] = 286;
+ @sysnum["timerfd_gettime"] = 287;
+ @sysnum["accept4"] = 288;
+ @sysnum["signalfd4"] = 289;
+ @sysnum["eventfd2"] = 290;
+ @sysnum["epoll_create1"] = 291;
+ @sysnum["dup3"] = 292;
+ @sysnum["pipe2"] = 293;
+ @sysnum["inotify_init1"] = 294;
+ @sysnum["preadv"] = 295;
+ @sysnum["pwritev"] = 296;
+ @sysnum["rt_tgsigqueueinfo"] = 297;
+ @sysnum["perf_event_open"] = 298;
+ @sysnum["recvmmsg"] = 299;
+ @sysnum["fanotify_init"] = 300;
+ @sysnum["fanotify_mark"] = 301;
+ @sysnum["prlimit64"] = 302;
+ @sysnum["name_to_handle_at"] = 303;
+ @sysnum["open_by_handle_at"] = 304;
+ @sysnum["clock_adjtime"] = 305;
+ @sysnum["syncfs"] = 306;
+ @sysnum["sendmmsg"] = 307;
+ @sysnum["setns"] = 308;
+ @sysnum["getcpu"] = 309;
+ @sysnum["process_vm_readv"] = 310;
+ @sysnum["process_vm_writev"] = 311;
+ @sysnum["kcmp"] = 312;
+ @sysnum["finit_module"] = 313;
+ @sysnum["sched_setattr"] = 314;
+ @sysnum["sched_getattr"] = 315;
+ @sysnum["renameat2"] = 316;
+ @sysnum["seccomp"] = 317;
+ @sysnum["getrandom"] = 318;
+ @sysnum["memfd_create"] = 319;
+ @sysnum["kexec_file_load"] = 320;
+ @sysnum["bpf"] = 321;
+ @sysnum["execveat"] = 322;
+ @sysnum["userfaultfd"] = 323;
+ @sysnum["membarrier"] = 324;
+ @sysnum["mlock2"] = 325;
+ @sysnum["copy_file_range"] = 326;
+ @sysnum["preadv2"] = 327;
+ @sysnum["pwritev2"] = 328;
+ @sysnum["pkey_mprotect"] = 329;
+ @sysnum["pkey_alloc"] = 330;
+ @sysnum["pkey_free"] = 331;
+ @sysnum["statx"] = 332;
+ @sysnum["io_pgetevents"] = 333;
+ @sysnum["rseq"] = 334;
+ printf("STARTED\n")
+}
+
+
+usdt:###INTERPRETER_PATH###:function__entry {
+ @["depth"]++;
+ @entrypoints[str(arg0)] = @["depth"];
+ @globals["previous_module"] = @globals["current_module"];
+ @globals["current_module"] = str(arg0);
+ printf("%s, %s, depth=%d\n", str(arg0), str(arg1), @["depth"]) ;
+ ###FUNCTION_ENTRY###
+}
+
+usdt:###INTERPRETER_PATH###:function__return {
+ @["depth"]--;
+ ###FUNCTION_EXIT###
+}
+
+tracepoint:raw_syscalls:sys_enter /comm == "python"/ {
+ ###SYSCALL_ENTRY###
+}
+
+END {
+ clear(@sysname);
+ clear(@sysnum);
+ clear(@entrypoints);
+}
\ No newline at end of file
diff --git a/src/secimport/templates/bpftrace/filters/file_system.bt b/src/secimport/templates/bpftrace/filters/file_system.bt
new file mode 100644
index 0000000..25cef75
--- /dev/null
+++ b/src/secimport/templates/bpftrace/filters/file_system.bt
@@ -0,0 +1 @@
+if (@sysname[args->id] == "open" || @sysname[args->id] == "write")
\ No newline at end of file
diff --git a/src/secimport/templates/bpftrace/filters/is_current_module_under_supervision.bt b/src/secimport/templates/bpftrace/filters/is_current_module_under_supervision.bt
new file mode 100644
index 0000000..617448b
--- /dev/null
+++ b/src/secimport/templates/bpftrace/filters/is_current_module_under_supervision.bt
@@ -0,0 +1 @@
+if(@entrypoints["###MODULE_NAME###"] != 0 && @["depth"] >= @entrypoints["###MODULE_NAME###"] && @entrypoints["###MODULE_NAME###"] >= @entrypoints[@globals["previous_module"]])
diff --git a/src/secimport/templates/bpftrace/filters/networking.bt b/src/secimport/templates/bpftrace/filters/networking.bt
new file mode 100644
index 0000000..83beecb
--- /dev/null
+++ b/src/secimport/templates/bpftrace/filters/networking.bt
@@ -0,0 +1 @@
+ if (@sysname[args->id] == "socket")
\ No newline at end of file
diff --git a/src/secimport/templates/bpftrace/filters/processes.bt b/src/secimport/templates/bpftrace/filters/processes.bt
new file mode 100644
index 0000000..45d9c93
--- /dev/null
+++ b/src/secimport/templates/bpftrace/filters/processes.bt
@@ -0,0 +1,13 @@
+ if (@sysname[args->id] == "posix_spawn" ||
+ @sysname[args->id] == "posix_spawnp" ||
+ @sysname[args->id] == "clone" ||
+ @sysname[args->id] == "__clone2" ||
+ @sysname[args->id] == "clone3" ||
+ @sysname[args->id] == "fork" ||
+ @sysname[args->id] == "vfork" ||
+ @sysname[args->id] == "forkexec" ||
+ @sysname[args->id] == "execl" ||
+ @sysname[args->id] == "execlp" ||
+ @sysname[args->id] == "execle" ||
+ @sysname[args->id] == "execv" ||
+ @sysname[args->id] == "execvp")
\ No newline at end of file
diff --git a/src/secimport/templates/bpftrace/probes/module_syscalls_allowlist_template.bt b/src/secimport/templates/bpftrace/probes/module_syscalls_allowlist_template.bt
new file mode 100644
index 0000000..55f82af
--- /dev/null
+++ b/src/secimport/templates/bpftrace/probes/module_syscalls_allowlist_template.bt
@@ -0,0 +1,20 @@
+/* ###MODULE_NAME### START */
+if ((@entrypoints["###MODULE_NAME###"] != 0) && (@["depth"] >= @entrypoints["###MODULE_NAME###"])){
+ if (@globals["latest_supervised_module"] == ""){
+ @globals["latest_supervised_module"] = "###MODULE_NAME###"
+ }
+ else{
+ if (@entrypoints["###MODULE_NAME###"] > @entrypoints[@globals["latest_supervised_module"]]){
+ @globals["latest_supervised_module"] = "###MODULE_NAME###"
+ }
+ }
+
+ if (@entrypoints["###MODULE_NAME###"] == @entrypoints[@globals["latest_supervised_module"]])
+ if (###SYSCALL_FILTER###){
+ 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###"], @globals["latest_supervised_module"], @entrypoints[@globals["latest_supervised_module"]]);
+ ###SUPERVISED_MODULES_FILTER###
+ ###SUPERVISED_MODULES_ACTION###
+ }
+ }
+}
+/* ###MODULE_NAME### END */
\ No newline at end of file
diff --git a/src/secimport/templates/actions/kill_on_processing.d b/src/secimport/templates/dtrace/actions/kill_on_processing.d
similarity index 100%
rename from src/secimport/templates/actions/kill_on_processing.d
rename to src/secimport/templates/dtrace/actions/kill_on_processing.d
diff --git a/src/secimport/templates/actions/kill_process.d b/src/secimport/templates/dtrace/actions/kill_process.d
similarity index 100%
rename from src/secimport/templates/actions/kill_process.d
rename to src/secimport/templates/dtrace/actions/kill_process.d
diff --git a/src/secimport/templates/actions/log_file_system.d b/src/secimport/templates/dtrace/actions/log_file_system.d
similarity index 100%
rename from src/secimport/templates/actions/log_file_system.d
rename to src/secimport/templates/dtrace/actions/log_file_system.d
diff --git a/src/secimport/templates/actions/log_network.d b/src/secimport/templates/dtrace/actions/log_network.d
similarity index 100%
rename from src/secimport/templates/actions/log_network.d
rename to src/secimport/templates/dtrace/actions/log_network.d
diff --git a/src/secimport/templates/actions/log_python_module_entry.d b/src/secimport/templates/dtrace/actions/log_python_module_entry.d
similarity index 100%
rename from src/secimport/templates/actions/log_python_module_entry.d
rename to src/secimport/templates/dtrace/actions/log_python_module_entry.d
diff --git a/src/secimport/templates/actions/log_python_module_exit.d b/src/secimport/templates/dtrace/actions/log_python_module_exit.d
similarity index 100%
rename from src/secimport/templates/actions/log_python_module_exit.d
rename to src/secimport/templates/dtrace/actions/log_python_module_exit.d
diff --git a/src/secimport/templates/actions/log_syscall.d b/src/secimport/templates/dtrace/actions/log_syscall.d
similarity index 100%
rename from src/secimport/templates/actions/log_syscall.d
rename to src/secimport/templates/dtrace/actions/log_syscall.d
diff --git a/src/secimport/templates/default.allowlist.template.d b/src/secimport/templates/dtrace/default.allowlist.template.d
similarity index 100%
rename from src/secimport/templates/default.allowlist.template.d
rename to src/secimport/templates/dtrace/default.allowlist.template.d
diff --git a/src/secimport/templates/default.blocklist.template.d b/src/secimport/templates/dtrace/default.blocklist.template.d
similarity index 100%
rename from src/secimport/templates/default.blocklist.template.d
rename to src/secimport/templates/dtrace/default.blocklist.template.d
diff --git a/src/secimport/templates/default.template.d b/src/secimport/templates/dtrace/default.template.d
similarity index 100%
rename from src/secimport/templates/default.template.d
rename to src/secimport/templates/dtrace/default.template.d
diff --git a/src/secimport/templates/default.yaml.template.d b/src/secimport/templates/dtrace/default.yaml.template.d
similarity index 100%
rename from src/secimport/templates/default.yaml.template.d
rename to src/secimport/templates/dtrace/default.yaml.template.d
diff --git a/src/secimport/templates/filters/file_system.d b/src/secimport/templates/dtrace/filters/file_system.d
similarity index 100%
rename from src/secimport/templates/filters/file_system.d
rename to src/secimport/templates/dtrace/filters/file_system.d
diff --git a/src/secimport/templates/filters/is_current_module_under_supervision.d b/src/secimport/templates/dtrace/filters/is_current_module_under_supervision.d
similarity index 100%
rename from src/secimport/templates/filters/is_current_module_under_supervision.d
rename to src/secimport/templates/dtrace/filters/is_current_module_under_supervision.d
diff --git a/src/secimport/templates/filters/networking.d b/src/secimport/templates/dtrace/filters/networking.d
similarity index 100%
rename from src/secimport/templates/filters/networking.d
rename to src/secimport/templates/dtrace/filters/networking.d
diff --git a/src/secimport/templates/filters/processes.d b/src/secimport/templates/dtrace/filters/processes.d
similarity index 100%
rename from src/secimport/templates/filters/processes.d
rename to src/secimport/templates/dtrace/filters/processes.d
diff --git a/src/secimport/templates/generate_profile.d b/src/secimport/templates/dtrace/generate_profile.d
similarity index 100%
rename from src/secimport/templates/generate_profile.d
rename to src/secimport/templates/dtrace/generate_profile.d
diff --git a/src/secimport/templates/headers/destructive.d b/src/secimport/templates/dtrace/headers/destructive.d
similarity index 100%
rename from src/secimport/templates/headers/destructive.d
rename to src/secimport/templates/dtrace/headers/destructive.d
diff --git a/src/secimport/templates/probes/module_syscalls_allowlist_template.d b/src/secimport/templates/dtrace/probes/module_syscalls_allowlist_template.d
similarity index 100%
rename from src/secimport/templates/probes/module_syscalls_allowlist_template.d
rename to src/secimport/templates/dtrace/probes/module_syscalls_allowlist_template.d
diff --git a/src/secimport/templates/py_sandbox.d b/src/secimport/templates/dtrace/py_sandbox.d
similarity index 100%
rename from src/secimport/templates/py_sandbox.d
rename to src/secimport/templates/dtrace/py_sandbox.d
diff --git a/tests/test_bpftrace_backend.py b/tests/test_bpftrace_backend.py
new file mode 100644
index 0000000..bf98808
--- /dev/null
+++ b/tests/test_bpftrace_backend.py
@@ -0,0 +1,154 @@
+import unittest
+import os
+import sys
+
+
+from secimport.backends.bpftrace_backend.bpftrace_backend import (
+ render_bpftrace_template,
+ create_bpftrace_script_for_module,
+ run_bpftrace_script_for_module,
+)
+
+
+class TestEBPFBackend(unittest.TestCase):
+ def test_run_bpftrace_script_for_module(self):
+ bpftrace_script_file_path = create_bpftrace_script_for_module(
+ "this",
+ allow_networking=False,
+ allow_shells=False,
+ log_file_system=True,
+ log_syscalls=True,
+ log_network=True,
+ log_python_calls=True,
+ destructive=True,
+ syscalls_allowlist=None,
+ )
+ self.assertEqual(
+ bpftrace_script_file_path, "/tmp/.secimport/bpftrace_sandbox_this.bt"
+ )
+ self.assertTrue(os.path.exists(bpftrace_script_file_path))
+ bpftrace_file_content = open(bpftrace_script_file_path).read()
+ self.assertTrue("system" in bpftrace_file_content)
+ self.assertTrue(sys.executable in bpftrace_file_content)
+
+ def test_create_bpftrace_script_for_module(self):
+ # create_bpftrace_script_for_module
+ # TODO: implement
+ bpftrace_script_file_path = create_bpftrace_script_for_module(
+ module_name="urllib",
+ allow_networking=False,
+ allow_shells=False,
+ log_file_system=True,
+ log_syscalls=True,
+ log_network=True,
+ log_python_calls=True,
+ destructive=True,
+ syscalls_allowlist=None,
+ )
+ bpftrace_file_content = open(bpftrace_script_file_path).read()
+ # print(bpftrace_file_content)
+ # Making sure all the template variables (start with '###') were successfully replaced in the function.
+ self.assertTrue("###" not in bpftrace_file_content)
+
+ def test_create_bpftrace_script_for_module_with_syscalls_allowlist(self):
+ syscall_allowlist = """
+ __mac_syscall
+ __pthread_canceled
+ bind
+ csrctl
+ fgetattrlist
+ getattrlist
+ getrlimit
+ listen
+ pipe
+ sendmsg_nocancel
+ shm_open
+ sigreturn
+ socketpair
+ sysctlbyname
+ __disable_threadsignal
+ accept
+ access
+ bsdthread_create
+ bsdthread_terminate
+ connect_nocancel
+ kqueue
+ openat
+ proc_info
+ readlink
+ recvfrom
+ shutdown
+ thread_selfid
+ gettimeofday
+ issetugid
+ select_nocancel
+ socket
+ write
+ getsockname
+ recvfrom_nocancel
+ sendto
+ kevent
+ psynch_cvsignal
+ psynch_cvwait
+ sysctl
+ sendto_nocancel
+ fcntl_nocancel
+ setsockopt
+ lstat64
+ fstatfs64
+ stat64
+ getdirentries64
+ munmap
+ read_nocancel
+ """.split()
+ bpftrace_script_file_path = create_bpftrace_script_for_module(
+ module_name="http",
+ allow_networking=False,
+ allow_shells=False,
+ log_file_system=True,
+ log_syscalls=True,
+ log_network=True,
+ log_python_calls=True,
+ destructive=True,
+ syscalls_allowlist=syscall_allowlist,
+ )
+ bpftrace_file_content = open(bpftrace_script_file_path).read()
+ self.assertTrue("system(" in bpftrace_file_content)
+ self.assertTrue("__mac_syscall" in bpftrace_file_content)
+ self.assertTrue("read_nocancel" in bpftrace_file_content)
+
+ def test_create_bpftrace_script_for_module_with_syscalls_blocklist(self):
+ syscall_blocklist = """
+ bind
+ """.split()
+ bpftrace_script_file_path = create_bpftrace_script_for_module(
+ module_name="http",
+ allow_networking=False,
+ allow_shells=False,
+ log_file_system=True,
+ log_syscalls=True,
+ log_network=True,
+ log_python_calls=True,
+ destructive=True,
+ syscalls_blocklist=syscall_blocklist,
+ )
+ bpftrace_file_content = open(bpftrace_script_file_path).read()
+ self.assertTrue("system(" in bpftrace_file_content)
+ self.assertTrue("bind" in bpftrace_file_content)
+
+ def test_render_bpftrace_template(self):
+ bpftrace_file_content = render_bpftrace_template(
+ module_traced_name="http",
+ allow_networking=False,
+ allow_shells=False,
+ log_file_system=True,
+ log_syscalls=True,
+ log_network=True,
+ log_python_calls=True,
+ destructive=True,
+ )
+ self.assertTrue("system(" in bpftrace_file_content)
+ self.assertTrue("http" in bpftrace_file_content)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/test_dtrace_backend.py b/tests/test_dtrace_backend.py
new file mode 100644
index 0000000..78166c4
--- /dev/null
+++ b/tests/test_dtrace_backend.py
@@ -0,0 +1,126 @@
+import unittest
+from secimport.backends.dtrace_backend.dtrace_backend import (
+ PROFILES_DIR_NAME,
+ build_module_sandbox_from_yaml_template,
+ create_dtrace_script_for_module,
+)
+import os
+
+
+class TestDtraceBackend(unittest.TestCase):
+ def test_create_dtrace_script_for_module(self):
+ dtrace_script_file_path = create_dtrace_script_for_module(
+ "this",
+ allow_networking=False,
+ allow_shells=False,
+ log_file_system=True,
+ log_syscalls=True,
+ log_network=True,
+ log_python_calls=True,
+ destructive=True,
+ syscalls_allowlist=None,
+ )
+ self.assertEqual(dtrace_script_file_path, "/tmp/.secimport/dtrace_sandbox_this.d")
+ self.assertTrue(os.path.exists(dtrace_script_file_path))
+ dtrace_file_content = open(dtrace_script_file_path).read()
+ self.assertTrue("#pragma D option destructive" in dtrace_file_content)
+
+ def test_non_destructive_mode_removes_destructive_header(self):
+ dtrace_script_file_path = create_dtrace_script_for_module(
+ "urllib",
+ allow_networking=False,
+ allow_shells=False,
+ log_file_system=True,
+ log_syscalls=True,
+ log_network=True,
+ log_python_calls=True,
+ destructive=False,
+ syscalls_allowlist=None,
+ )
+ dtrace_file_content = open(dtrace_script_file_path).read()
+ self.assertTrue("###DESTRUCTIVE###" not in dtrace_file_content)
+
+ def test_syscall_allowlist_script_generation(self):
+ syscall_allowlist = """
+ __mac_syscall
+ __pthread_canceled
+ bind
+ csrctl
+ fgetattrlist
+ getattrlist
+ getrlimit
+ listen
+ pipe
+ sendmsg_nocancel
+ shm_open
+ sigreturn
+ socketpair
+ sysctlbyname
+ __disable_threadsignal
+ accept
+ access
+ bsdthread_create
+ bsdthread_terminate
+ connect_nocancel
+ kqueue
+ openat
+ proc_info
+ readlink
+ recvfrom
+ shutdown
+ thread_selfid
+ gettimeofday
+ issetugid
+ select_nocancel
+ socket
+ write
+ getsockname
+ recvfrom_nocancel
+ sendto
+ kevent
+ psynch_cvsignal
+ psynch_cvwait
+ sysctl
+ sendto_nocancel
+ fcntl_nocancel
+ setsockopt
+ lstat64
+ fstatfs64
+ stat64
+ getdirentries64
+ munmap
+ read_nocancel
+ """.split()
+ dtrace_script_file_path = create_dtrace_script_for_module(
+ "http",
+ allow_networking=False,
+ allow_shells=False,
+ log_file_system=True,
+ log_syscalls=True,
+ log_network=True,
+ log_python_calls=True,
+ destructive=False,
+ syscalls_allowlist=syscall_allowlist,
+ )
+ dtrace_file_content = open(dtrace_script_file_path).read()
+ self.assertTrue("#pragma D option destructive" in dtrace_file_content)
+ self.assertTrue("__mac_syscall" in dtrace_file_content)
+ self.assertTrue("read_nocancel" in dtrace_file_content)
+
+ def test_build_module_sandbox_from_yaml_template(self):
+ profile_file_path = PROFILES_DIR_NAME / "example.yaml"
+ module_sandbox_code: str = build_module_sandbox_from_yaml_template(
+ profile_file_path
+ )
+ self.assertTrue("fastapi" in module_sandbox_code)
+ self.assertTrue("requests" in module_sandbox_code)
+ self.assertTrue("uvicorn" in module_sandbox_code)
+ # with open("/tmp/.secimport/example_sandbox.d", "w") as example_sandbox:
+ # example_sandbox.write(module_sandbox_code)
+
+ self.assertIsInstance(module_sandbox_code, str)
+ self.assertFalse("###SUPERVISED_MODULES_PROBES###" in module_sandbox_code)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/test_sandbox_helper.py b/tests/test_sandbox_helper.py
index e16c6ec..3490cf9 100644
--- a/tests/test_sandbox_helper.py
+++ b/tests/test_sandbox_helper.py
@@ -1,9 +1,90 @@
-from unittest import TestCase
-from secimport.sandbox_helper import secure_import, create_dtrace_script_for_module
-import os
-
-
-class TestSecImport(TestCase):
+import unittest
+
+from secimport.sandbox_helper import secure_import
+
+EXAMPLE_SYSCALL_LIST = """
+access
+exit
+getentropy
+getrlimit
+mprotect
+shm_open
+sysctlbyname
+write
+fcntl_nocancel
+readlink
+fcntl
+mmap
+fstatfs64
+getdirentries64
+read_nocancel
+madvise
+close_nocancel
+open_nocancel
+sigaction
+close
+open
+lseek
+read
+fstat64
+ioctl
+stat64
+ __mac_syscall
+__pthread_canceled
+bind
+csrctl
+fgetattrlist
+getattrlist
+getrlimit
+listen
+pipe
+sendmsg_nocancel
+shm_open
+sigreturn
+socketpair
+sysctlbyname
+__disable_threadsignal
+accept
+access
+bsdthread_create
+bsdthread_terminate
+connect_nocancel
+kqueue
+openat
+proc_info
+readlink
+recvfrom
+shutdown
+thread_selfid
+gettimeofday
+issetugid
+select_nocancel
+socket
+write
+getsockname
+recvfrom_nocancel
+sendto
+kevent
+psynch_cvsignal
+psynch_cvwait
+sysctl
+sendto_nocancel
+fcntl_nocancel
+setsockopt
+lstat64
+fstatfs64
+stat64
+getdirentries64
+munmap
+read_nocancel
+dup2
+ftruncate
+chdir
+sigaltstack
+""".split()
+
+
+class TestSecImport(unittest.TestCase):
def test_import_with_shell_true(self):
secure_import("urllib")
a = [_**9 for _ in range(100)]
@@ -13,237 +94,24 @@ def test_import_with_shell_false(self):
module = secure_import("this")
self.assertEqual(module.__name__, "this")
- def test_create_dtrace_script_for_module(self):
- dtrace_script_file_path = create_dtrace_script_for_module(
- "this",
- allow_networking=False,
- allow_shells=False,
- log_file_system=True,
- log_syscalls=True,
- log_network=True,
- log_python_calls=True,
- destructive=True,
- syscalls_allowlist=None,
- )
- self.assertEqual(dtrace_script_file_path, "/tmp/.secimport/sandbox_this.d")
- self.assertTrue(os.path.exists(dtrace_script_file_path))
- dtrace_file_content = open(dtrace_script_file_path).read()
- self.assertTrue("#pragma D option destructive" in dtrace_file_content)
-
- def test_non_destructive_mode_removes_destructive_header(self):
- dtrace_script_file_path = create_dtrace_script_for_module(
- "urllib",
- allow_networking=False,
- allow_shells=False,
- log_file_system=True,
- log_syscalls=True,
- log_network=True,
- log_python_calls=True,
- destructive=False,
- syscalls_allowlist=None,
- )
- dtrace_file_content = open(dtrace_script_file_path).read()
- self.assertTrue("###DESTRUCTIVE###" not in dtrace_file_content)
-
- def test_syscall_allowlist_script_generation(self):
- syscall_allowlist = """
- __mac_syscall
- __pthread_canceled
- bind
- csrctl
- fgetattrlist
- getattrlist
- getrlimit
- listen
- pipe
- sendmsg_nocancel
- shm_open
- sigreturn
- socketpair
- sysctlbyname
- __disable_threadsignal
- accept
- access
- bsdthread_create
- bsdthread_terminate
- connect_nocancel
- kqueue
- openat
- proc_info
- readlink
- recvfrom
- shutdown
- thread_selfid
- gettimeofday
- issetugid
- select_nocancel
- socket
- write
- getsockname
- recvfrom_nocancel
- sendto
- kevent
- psynch_cvsignal
- psynch_cvwait
- sysctl
- sendto_nocancel
- fcntl_nocancel
- setsockopt
- lstat64
- fstatfs64
- stat64
- getdirentries64
- munmap
- read_nocancel
- """.split()
- dtrace_script_file_path = create_dtrace_script_for_module(
- "http",
- allow_networking=False,
- allow_shells=False,
- log_file_system=True,
- log_syscalls=True,
- log_network=True,
- log_python_calls=True,
- destructive=False,
- syscalls_allowlist=syscall_allowlist,
- )
- dtrace_file_content = open(dtrace_script_file_path).read()
- self.assertTrue("#pragma D option destructive" in dtrace_file_content)
- self.assertTrue("__mac_syscall" in dtrace_file_content)
- self.assertTrue("read_nocancel" in dtrace_file_content)
-
def test_syscall_allowlist_secure_import(self):
module = secure_import(
module_name="http",
- syscalls_allowlist="""
- access
- exit
- getentropy
- getrlimit
- mprotect
- shm_open
- sysctlbyname
- write
- fcntl_nocancel
- readlink
- fcntl
- mmap
- fstatfs64
- getdirentries64
- read_nocancel
- madvise
- close_nocancel
- open_nocancel
- sigaction
- close
- open
- lseek
- read
- fstat64
- ioctl
- stat64
- __mac_syscall
- __pthread_canceled
- bind
- csrctl
- fgetattrlist
- getattrlist
- getrlimit
- listen
- pipe
- sendmsg_nocancel
- shm_open
- sigreturn
- socketpair
- sysctlbyname
- __disable_threadsignal
- accept
- access
- bsdthread_create
- bsdthread_terminate
- connect_nocancel
- kqueue
- openat
- proc_info
- readlink
- recvfrom
- shutdown
- thread_selfid
- gettimeofday
- issetugid
- select_nocancel
- socket
- write
- getsockname
- recvfrom_nocancel
- sendto
- kevent
- psynch_cvsignal
- psynch_cvwait
- sysctl
- sendto_nocancel
- fcntl_nocancel
- setsockopt
- lstat64
- fstatfs64
- stat64
- getdirentries64
- munmap
- read_nocancel
- dup2
- ftruncate
- chdir
- sigaltstack
- """.split(),
+ syscalls_allowlist=EXAMPLE_SYSCALL_LIST,
+ log_file_system=True,
+ log_python_calls=True,
)
self.assertEqual(module.__name__, "http")
- def test_render_probe_for_module(self):
- from secimport.sandbox_helper import _render_probe_for_module
-
- probe_text = _render_probe_for_module(
- module_name="this.py",
- destructive=True,
- syscalls_allowlist=["open", "accept", "select"],
- )
- with open("/tmp/.secimport/example_probe.d", "w") as example_probe:
- example_probe.write(probe_text)
-
- self.assertTrue("open" in probe_text)
- self.assertTrue("accept" in probe_text)
- self.assertTrue("select" in probe_text)
- self.assertTrue("kill" in probe_text)
-
- probe_text = _render_probe_for_module(
- module_name="yaml.py",
+ def test_syscall_blocklist_secure_import(self):
+ # Only log violations
+ module = secure_import(
+ module_name="http",
+ syscalls_blocklist=EXAMPLE_SYSCALL_LIST,
destructive=False,
- syscalls_allowlist=["read", "ioctl", "fstat64"],
)
-
- self.assertTrue("fstat64" in probe_text)
- self.assertTrue("read" in probe_text)
- self.assertTrue("ioctl" in probe_text)
- self.assertFalse("kill" in probe_text)
-
- def test_build_module_sandbox_from_yaml_template(self):
- from secimport.sandbox_helper import (
- build_module_sandbox_from_yaml_template,
- PROFILES_DIR_NAME,
- )
-
- profile_file_path = PROFILES_DIR_NAME / "example.yaml"
- module_sandbox_code: str = build_module_sandbox_from_yaml_template(
- profile_file_path
- )
- self.assertTrue("fastapi" in module_sandbox_code)
- self.assertTrue("requests" in module_sandbox_code)
- self.assertTrue("uvicorn" in module_sandbox_code)
- # with open("/tmp/.secimport/example_sandbox.d", "w") as example_sandbox:
- # example_sandbox.write(module_sandbox_code)
-
- self.assertIsInstance(module_sandbox_code, str)
- self.assertFalse("###SUPERVISED_MODULES_PROBES###" in module_sandbox_code)
+ self.assertEqual(module.__name__, "http")
-if __name__ == '__main__':
-
\ No newline at end of file
+if __name__ == "__main__":
+ unittest.main()