pax_global_header00006660000000000000000000000064147743202730014523gustar00rootroot0000000000000052 comment=1a5085c31a4e15b371e632a67b5c93a180901e0a landrun-0.1.15/000077500000000000000000000000001477432027300132525ustar00rootroot00000000000000landrun-0.1.15/.github/000077500000000000000000000000001477432027300146125ustar00rootroot00000000000000landrun-0.1.15/.github/workflows/000077500000000000000000000000001477432027300166475ustar00rootroot00000000000000landrun-0.1.15/.github/workflows/build.yml000066400000000000000000000011221477432027300204650ustar00rootroot00000000000000name: Build on: push: branches: [main] pull_request: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: "1.24.1" check-latest: true - name: Install dependencies run: go mod download - name: Build run: go build -v -o landrun ./cmd/landrun/main.go - name: Upload binary uses: actions/upload-artifact@v4 with: name: landrun-linux-amd64 path: ./landrun landrun-0.1.15/.github/workflows/go-compatibility.yml000066400000000000000000000007761477432027300226600ustar00rootroot00000000000000name: Go version compatibility on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: go: ["1.18", "1.20", "1.22", "1.24"] name: Go ${{ matrix.go }} build steps: - uses: actions/checkout@v3 - name: Set up Go ${{ matrix.go }} uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} - name: Download dependencies run: go mod tidy - name: Build landrun run: go build ./cmd/landrun landrun-0.1.15/.gitignore000066400000000000000000000011061477432027300152400ustar00rootroot00000000000000# If you prefer the allow list template instead of the deny list, see community template: # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ # Go workspace file go.work go.work.sum # env file .env main tmp internal/sandbox/test_rw internal/sandbox/test_ro test_env landrunlandrun-0.1.15/LICENSE000066400000000000000000000020561477432027300142620ustar00rootroot00000000000000MIT License Copyright (c) 2025 Armin ranjbar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. landrun-0.1.15/README.md000066400000000000000000000336071477432027300145420ustar00rootroot00000000000000# Landrun A lightweight, secure sandbox for running Linux processes using Landlock. Think firejail, but with kernel-level security and minimal overhead. Linux Landlock is a kernel-native security module that lets unprivileged processes sandbox themselves. Landrun is designed to make it practical to sandbox any command with fine-grained filesystem and network access controls. No root. No containers. No SELinux/AppArmor configs. It's lightweight, auditable, and wraps Landlock v5 features (file access + TCP restrictions). ## Features - 🔒 Kernel-level security using Landlock - 🚀 Lightweight and fast execution - 🛡️ Fine-grained access control for directories and files - 🔄 Support for read and write paths - ⚡ Path-specific execution permissions - 🌐 TCP network access control (binding and connecting) ## Demo

landrun demo

## Requirements - Linux kernel 5.13 or later with Landlock enabled - Linux kernel 6.7 or later for network restrictions (TCP bind/connect) - Go 1.18 or later (for building from source) ## Installation ### Quick Install ```bash go install github.com/zouuup/landrun/cmd/landrun@latest ``` ### From Source ```bash git clone https://github.com/zouuup/landrun.git cd landrun go build -o landrun cmd/landrun/main.go sudo cp landrun /usr/local/bin/ ``` ### Distros #### Arch (AUR) maintained by [Vcalv](https://github.com/vcalv) [AUR](https://aur.archlinux.org/packages/landrun-git) ```bash yay -S landrun ``` #### Slackware maintained by [r1w1s1](https://github.com/r1w1s1) [Slackbuild](https://slackbuilds.org/repository/15.0/network/landrun/?search=landrun) ```bash sudo sbopkg -i packagename ``` ## Usage Basic syntax: ```bash landrun [options] [args...] ``` ### Options - `--ro `: Allow read-only access to specified path (can be specified multiple times or as comma-separated values) - `--rox `: Allow read-only access with execution to specified path (can be specified multiple times or as comma-separated values) - `--rw `: Allow read-write access to specified path (can be specified multiple times or as comma-separated values) - `--rwx `: Allow read-write access with execution to specified path (can be specified multiple times or as comma-separated values) - `--bind-tcp `: Allow binding to specified TCP port (can be specified multiple times or as comma-separated values) - `--connect-tcp `: Allow connecting to specified TCP port (can be specified multiple times or as comma-separated values) - `--env `: Environment variable to pass to the sandboxed command (format: KEY=VALUE or just KEY to pass current value) - `--best-effort`: Use best effort mode, falling back to less restrictive sandbox if necessary [default: disabled] - `--log-level `: Set logging level (error, info, debug) [default: "error"] - `--unrestricted-network`: Allows unrestricted network access (disables all network restrictions) - `--unrestricted-filesystem`: Allows unrestricted filesystem access (disables all filesystem restrictions) - `--add-exec`: Automatically adds the executing binary to --rox - `--ldd`: Automatically adds required libraries to --rox ### Important Notes - You must explicitly add the directory or files to the command you want to run with `--rox` flag - For system commands, you typically need to include `/usr/bin`, `/usr/lib`, and other system directories - Use `--rwx` for directories or files where you need both write access and the ability to execute files - Network restrictions require Linux kernel 6.7 or later with Landlock ABI v4 - By default, no environment variables are passed to the sandboxed command. Use `--env` to explicitly pass environment variables - The `--best-effort` flag allows graceful degradation on older kernels that don't support all requested restrictions - Paths can be specified either using multiple flags or as comma-separated values (e.g., `--ro /usr,/lib,/home`) - If no paths or network rules are specified and neither unrestricted flag is set, landrun will apply maximum restrictions (denying all access) ### Environment Variables - `LANDRUN_LOG_LEVEL`: Set logging level (error, info, debug) ### Examples 1. Run a command that allows exec access to a specific file ```bash landrun --rox /usr/bin/ls --rox /usr/lib --ro /home ls /home ``` 2. Run a command with read-only access to a directory: ```bash landrun --rox /usr/ --ro /path/to/dir ls /path/to/dir ``` 3. Run a command with write access to a directory: ```bash landrun --rox /usr/bin --ro /lib --rw /path/to/dir touch /path/to/dir/newfile ``` 4. Run a command with write access to a file: ```bash landrun --rox /usr/bin --ro /lib --rw /path/to/dir/newfile touch /path/to/dir/newfile ``` 5. Run a command with execution permissions: ```bash landrun --rox /usr/ --ro /lib,/lib64 /usr/bin/bash ``` 6. Run with debug logging: ```bash landrun --log-level debug --rox /usr/ --ro /lib,/lib64,/path/to/dir ls /path/to/dir ``` 7. Run with network restrictions: ```bash landrun --rox /usr/ --ro /lib,/lib64 --bind-tcp 8080 --connect-tcp 80 /usr/bin/my-server ``` This will allow the program to only bind to TCP port 8080 and connect to TCP port 80. 8. Run a DNS client with appropriate permissions: ```bash landrun --log-level debug --ro /etc,/usr --rox /usr/ --connect-tcp 443 nc kernel.org 443 ``` This allows connections to port 443, requires access to /etc/resolv.conf for resolving DNS. 9. Run a web server with selective network permissions: ```bash landrun --rox /usr/bin --ro /lib,/lib64,/var/www --rwx /var/log --bind-tcp 80,443 /usr/bin/nginx ``` 10. Running anything without providing parameters is... maximum security jail! ```bash landrun ls ``` 11. If you keep getting permission denied without knowing what exactly going on, best to use strace with it. ```bash landrun --rox /usr strace -f -e trace=all ls ``` 12. Run with specific environment variables: ```bash landrun --rox /usr --ro /etc --env HOME --env PATH --env CUSTOM_VAR=my_value -- env ``` 13. Run command with explicity access to files instead of directories: ```bash landrun --rox /usr/lib/libc.so.6 --rox /usr/lib64/ld-linux-x86-64.so.2 --rox /usr/bin/true /usr/bin/true ``` 14. Run a command with --add-exec which automatically adds target binary to --rox ```bash landrun --rox /usr/lib/ --add-exec /usr/bin/true ``` 15. Run a command with --ldd and --add-exec which automatically adds required libraries and target binary to --rox ```bash landrun --ldd --add-exec /usr/bin/true ``` Note that shared libs always need exec permission due to how they are loaded, PROT_EXEC on mmap() etc. This example passes the current HOME and PATH variables, plus a custom variable named CUSTOM_VAR. ## Systemd Integration landrun can be integrated with systemd to run services with enhanced security. Here's an example of running nginx with landrun: 1. Create a systemd service file (e.g., `/etc/systemd/system/nginx-landrun.service`): ```ini [Unit] Description=nginx with landrun sandbox After=network.target [Service] Type=simple ExecStart=/usr/bin/landrun \ --rox /usr/bin,/usr/lib \ --ro /etc/nginx,/etc/ssl,/etc/passwd,/etc/group,/etc/nsswitch.conf \ --rwx /var/log/nginx \ --rwx /var/cache/nginx \ --bind-tcp 80,443 \ /usr/bin/nginx -g 'daemon off;' Restart=always User=nginx Group=nginx [Install] WantedBy=multi-user.target ``` 2. Enable and start the service: ```bash sudo systemctl daemon-reload sudo systemctl enable nginx-landrun sudo systemctl start nginx-landrun ``` 3. Check the service status: ```bash sudo systemctl status nginx-landrun ``` This configuration: - Runs nginx with minimal required permissions - Allows binding to ports 80 and 443 - Provides read-only access to configuration files - Allows write access only to log and cache directories - Runs as the nginx user and group - Automatically restarts on failure You can adjust the permissions based on your specific needs. For example, if you need to serve static files from `/var/www`, add `--ro /var/www` to the ExecStart line. ## Security landrun uses Linux's Landlock to create a secure sandbox environment. It provides: - File system access control - Directory access restrictions - Execution control - TCP network restrictions - Process isolation - Default restrictive mode when no rules are specified Landlock is an access-control system that enables processes to securely restrict themselves and their future children. As a stackable Linux Security Module (LSM), it creates additional security layers on top of existing system-wide access controls, helping to mitigate security impacts from bugs or malicious behavior in applications. ### Landlock Access Control Rights landrun leverages Landlock's fine-grained access control mechanisms, which include: **File-specific rights:** - Execute files (`LANDLOCK_ACCESS_FS_EXECUTE`) - Write to files (`LANDLOCK_ACCESS_FS_WRITE_FILE`) - Read files (`LANDLOCK_ACCESS_FS_READ_FILE`) - Truncate files (`LANDLOCK_ACCESS_FS_TRUNCATE`) - Available since Landlock ABI v3 - IOCTL operations on devices (`LANDLOCK_ACCESS_FS_IOCTL_DEV`) - Available since Landlock ABI v5 **Directory-specific rights:** - Read directory contents (`LANDLOCK_ACCESS_FS_READ_DIR`) - Remove directories (`LANDLOCK_ACCESS_FS_REMOVE_DIR`) - Remove files (`LANDLOCK_ACCESS_FS_REMOVE_FILE`) - Create various filesystem objects (char devices, directories, regular files, sockets, etc.) - Refer/reparent files across directories (`LANDLOCK_ACCESS_FS_REFER`) - Available since Landlock ABI v2 **Network-specific rights** (requires Linux 6.7+ with Landlock ABI v4): - Bind to specific TCP ports (`LANDLOCK_ACCESS_NET_BIND_TCP`) - Connect to specific TCP ports (`LANDLOCK_ACCESS_NET_CONNECT_TCP`) ### Limitations - Landlock must be supported by your kernel - Network restrictions require Linux kernel 6.7 or later with Landlock ABI v4 - Some operations may require additional permissions - Files or directories opened before sandboxing are not subject to Landlock restrictions ## Kernel Compatibility Table | Feature | Minimum Kernel Version | Landlock ABI Version | | ---------------------------------- | ---------------------- | -------------------- | | Basic filesystem sandboxing | 5.13 | 1 | | File referring/reparenting control | 5.19 | 2 | | File truncation control | 6.2 | 3 | | Network TCP restrictions | 6.7 | 4 | | IOCTL on special files | 6.10 | 5 | ## Troubleshooting If you receive "permission denied" or similar errors: 1. Ensure you've added all necessary paths with `--ro` or `--rw` 2. Try running with `--log-level debug` to see detailed permission information 3. Check that Landlock is supported and enabled on your system: ```bash grep -E 'landlock|lsm=' /boot/config-$(uname -r) # alternatively, if there are no /boot/config-* files zgrep -iE 'landlock|lsm=' /proc/config.gz # another alternate method grep -iE 'landlock|lsm=' /lib/modules/$(uname -r)/config ``` You should see `CONFIG_SECURITY_LANDLOCK=y` and `lsm=landlock,...` in the output 4. For network restrictions, verify your kernel version is 6.7+ with Landlock ABI v4: ```bash uname -r ``` ## Technical Details ### Implementation This project uses the [landlock-lsm/go-landlock](https://github.com/landlock-lsm/go-landlock) package for sandboxing, which provides both filesystem and network restrictions. The current implementation supports: - Read/write/execute restrictions for files and directories - TCP port binding restrictions - TCP port connection restrictions - Best-effort mode for graceful degradation on older kernels ### Best-Effort Mode When using `--best-effort` (disabled by default), landrun will gracefully degrade to using the best available Landlock version on the current kernel. This means: - On Linux 6.7+: Full filesystem and network restrictions - On Linux 6.2-6.6: Filesystem restrictions including truncation, but no network restrictions - On Linux 5.19-6.1: Basic filesystem restrictions including file reparenting, but no truncation control or network restrictions - On Linux 5.13-5.18: Basic filesystem restrictions without file reparenting, truncation control, or network restrictions - On older Linux: No restrictions (sandbox disabled) When no rules are specified and neither unrestricted flag is set, landrun will apply maximum restrictions available for the current kernel version. ### Tests The project includes a comprehensive test suite that verifies: - Basic filesystem access controls (read-only, read-write, execute) - Directory traversal and path handling - Network restrictions (TCP bind/connect) - Environment variable isolation - System command execution - Edge cases and regression tests Run the tests with: ```bash ./test.sh ``` Use `--keep-binary` to preserve the test binary after completion: ```bash ./test.sh --keep-binary ``` Use `--use-system` to test against the system-installed landrun binary: ```bash ./test.sh --use-system ``` ## Future Features Based on the Linux Landlock API capabilities, we plan to add: - 🔒 Enhanced filesystem controls with more fine-grained permissions - 🌐 Support for UDP and other network protocol restrictions (when supported by Linux kernel) - 🔄 Process scoping and resource controls - 🛡️ Additional security features as they become available in the Landlock API ## Acknowledgements This project wouldn't exist without: - [Landlock](https://landlock.io), the kernel security module enabling unprivileged sandboxing - maintained by [@l0kod](https://github.com/l0kod) - [go-landlock](https://github.com/landlock-lsm/go-landlock), the Go bindings powering this tool - developed by [@gnoack](https://github.com/gnoack) ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. landrun-0.1.15/cmd/000077500000000000000000000000001477432027300140155ustar00rootroot00000000000000landrun-0.1.15/cmd/landrun/000077500000000000000000000000001477432027300154605ustar00rootroot00000000000000landrun-0.1.15/cmd/landrun/main.go000066400000000000000000000132361477432027300167400ustar00rootroot00000000000000package main import ( "os" osexec "os/exec" "strings" "github.com/urfave/cli/v2" "github.com/zouuup/landrun/internal/exec" "github.com/zouuup/landrun/internal/log" "github.com/zouuup/landrun/internal/sandbox" ) // Version is the current version of landrun const Version = "0.1.15" // getLibraryDependencies returns a list of library paths that the given binary depends on func getLibraryDependencies(binary string) ([]string, error) { cmd := osexec.Command("ldd", binary) output, err := cmd.Output() if err != nil { return nil, err } var libPaths []string lines := strings.Split(string(output), "\n") for _, line := range lines { // Skip empty lines and the first line (usually the binary name) if line == "" || !strings.Contains(line, "=>") { continue } // Extract the library path parts := strings.Fields(line) if len(parts) >= 3 { libPath := strings.Trim(parts[2], "()") if libPath != "" { libPaths = append(libPaths, libPath) } } } return libPaths, nil } func main() { app := &cli.App{ Name: "landrun", Usage: "Run a command in a Landlock sandbox", Version: Version, Flags: []cli.Flag{ &cli.StringFlag{ Name: "log-level", Usage: "Set logging level (error, info, debug)", Value: "error", EnvVars: []string{"LANDRUN_LOG_LEVEL"}, }, &cli.StringSliceFlag{ Name: "ro", Usage: "Allow read-only access to this path", }, &cli.StringSliceFlag{ Name: "rox", Usage: "Allow read-only access with execution to this path", }, &cli.StringSliceFlag{ Name: "rw", Usage: "Allow read-write access to this path", }, &cli.StringSliceFlag{ Name: "rwx", Usage: "Allow read-write access with execution to this path", }, &cli.IntSliceFlag{ Name: "bind-tcp", Usage: "Allow binding to these TCP ports", Hidden: false, }, &cli.IntSliceFlag{ Name: "connect-tcp", Usage: "Allow connecting to these TCP ports", Hidden: false, }, &cli.BoolFlag{ Name: "best-effort", Usage: "Use best effort mode (fall back to less restrictive sandbox if necessary)", Value: false, }, &cli.StringSliceFlag{ Name: "env", Usage: "Environment variables to pass to the sandboxed command (KEY=VALUE or just KEY to pass current value)", Value: cli.NewStringSlice(), }, &cli.BoolFlag{ Name: "unrestricted-filesystem", Usage: "Allow unrestricted filesystem access", Value: false, }, &cli.BoolFlag{ Name: "unrestricted-network", Usage: "Allow unrestricted network access", Value: false, }, &cli.BoolFlag{ Name: "ldd", Usage: "Automatically detect and add library dependencies to --rox", Value: false, }, &cli.BoolFlag{ Name: "add-exec", Usage: "Automatically add the executable path to --rox", Value: false, }, }, Before: func(c *cli.Context) error { log.SetLevel(c.String("log-level")) return nil }, Action: func(c *cli.Context) error { args := c.Args().Slice() if len(args) == 0 { log.Fatal("Missing command to run") } // Combine --ro and --rox paths for read-only access readOnlyPaths := append([]string{}, c.StringSlice("ro")...) readOnlyPaths = append(readOnlyPaths, c.StringSlice("rox")...) // Combine --rw and --rwx paths for read-write access readWritePaths := append([]string{}, c.StringSlice("rw")...) readWritePaths = append(readWritePaths, c.StringSlice("rwx")...) // Combine --rox and --rwx paths for executable permissions readOnlyExecutablePaths := append([]string{}, c.StringSlice("rox")...) readWriteExecutablePaths := append([]string{}, c.StringSlice("rwx")...) binary, err := osexec.LookPath(args[0]) if err != nil { log.Fatal("Failed to find binary: %v", err) } // Add command's directory to readOnlyExecutablePaths if c.Bool("add-exec") { readOnlyExecutablePaths = append(readOnlyExecutablePaths, binary) log.Debug("Added executable path: %v", binary) } // If --ldd flag is set, detect and add library dependencies if c.Bool("ldd") { libPaths, err := getLibraryDependencies(binary) if err != nil { log.Fatal("Failed to detect library dependencies: %v", err) } // Add library directories to readOnlyExecutablePaths readOnlyExecutablePaths = append(readOnlyExecutablePaths, libPaths...) log.Debug("Added library paths: %v", libPaths) } cfg := sandbox.Config{ ReadOnlyPaths: readOnlyPaths, ReadWritePaths: readWritePaths, ReadOnlyExecutablePaths: readOnlyExecutablePaths, ReadWriteExecutablePaths: readWriteExecutablePaths, BindTCPPorts: c.IntSlice("bind-tcp"), ConnectTCPPorts: c.IntSlice("connect-tcp"), BestEffort: c.Bool("best-effort"), UnrestrictedFilesystem: c.Bool("unrestricted-filesystem"), UnrestrictedNetwork: c.Bool("unrestricted-network"), } // Process environment variables envVars := processEnvironmentVars(c.StringSlice("env")) if err := sandbox.Apply(cfg); err != nil { log.Fatal("Failed to apply sandbox: %v", err) } return exec.Run(args, envVars) }, } if err := app.Run(os.Args); err != nil { log.Fatal("%v", err) } } // processEnvironmentVars processes the env flag values func processEnvironmentVars(envFlags []string) []string { result := []string{} for _, env := range envFlags { // If the flag is just a key (no = sign), get the value from the current environment if !strings.Contains(env, "=") { if val, exists := os.LookupEnv(env); exists { result = append(result, env+"="+val) } } else { // Flag already contains the value (KEY=VALUE format) result = append(result, env) } } return result } landrun-0.1.15/demo.gif000066400000000000000000000642031477432027300146720ustar00rootroot00000000000000GIF89a3! NETSCAPE2.0!gif.ski!>,3ڋ޼H扦ʶ  ĢLnʦ JԪjܮ N (8HXhx)9IYiy ' 9JZjz  *k{F+ ,<\Ia,-=MM\y .>=n~VO_o3o,0Bp:|YÈ+Zt5ƍ+e2HBG<2ʕ,[| 3̙4kڼ3Ν<{ 4СD=4ҥL:} 5ԩTZ5֭\z 6رd˚=6ڵlۺ} 7ܹtڽ7޽| 8 >8Ō;~ 9ɔ+[9͜;{ :ѤK>:լ[~ ;ٴk۾;ݼ{ <ċ?<̛;=ԫ[=ܻ{>˛?>ۻ?ۿ?`H`` .`>aNHa^ana~b"Hb&b*b.c2Hc6ވc:c>dBIdFdJ.dN> eRNIeV^eZne^~ fbIfffjfn grIgvމgzg~ hJhh.h> iNJi^ini~ jJjjj kJkފkk lKll.l> mNKm^mnm~ nKn枋nn oKoދoo pLpp /p? qOLq_qoq r"Lr&r*r. s2Ls6ߌs:s> tBMtFtJ/tN? uROMuV_uZou^ vbMvfvjvn wrMwvߍwzw~ xNxx/x?yONy喿_yoyz袏Nz馟zꪯz뮿{N{ߎ{{|O||/|?}OO}_}o}~O~柏~~Oߏ p,*p lJp/ jp?ƒ " K!, +@@@000``` ???pppPPPПooo___OOOcccKKKÇp8Ȥrl:ШtJZV{zxL.zn|Nۛ}go}jyD{[SEl ` P ^\G HJIJH JQFP FI WBdHOYadZ[Y)\ȰÇp THBH<h{L"Q Y-Tx`qLdy\G V]1"( a@ T3`*V(""KٳT8`NlGiA,^$MDu Nk%jIFQTwE0)A#8di 3A>`7g9x&°#pc @,ӫ_W L ~j#@ Lp|H OXW@ Q P H( a0Ula1 *ƅ"S0b}H  ݈ )k@V@Tj$P@W!ޚl曞EP;<;"DHǟ*&lXNA U@P;,RnW@s>)اba$}U#6JVaZTZ Z @qxkT@LDm,j©vmjaPY(Xց%JX$I@E@(Dl`a*)5aXOaDՉŻEkO[:QS+P$PۈnEP$%eDmr`@ $bpπ95&Ҏ.uTO:2KA  H\kUC]xrԄ8UX3$Q7AMc砇~5`)xy}0`0D@SC1@Śڟ K|!OBMbUʲM`Z^WǖlVcT[>ѹ${[PqЀ娅(}b rP,'*5"TLxHPB(ED?r8&3~S\KJPz]% >'a, FAoЧ?7ю*YFr@,A*Lgёrh (Mӝ@ "t*T HMRʆ !(, r@@@```000 pppа???PPPooo___OOOcccKKKÇ` #`hlp,tmx>pH,Ȥrl:ШDNجvxL.tJRp~ Lm9|7  y 8H' '(?/sAp#%9/MG '<(]+": KF5(B 0,Xm > *`p@ d0:p0j8bp"DL|HHi"~X0 DJHO B M2 v hAJF DXٲU -;1XH#{,GݻxY+A [ Ā h Ɨ'PZ09A\Ŷc{4@Ab# (pqp&&[mtHI+ʭXbrj^:El vۆ ]Ͼ{S{Iw,+.,M>1^ @K7ԙ H_F..6 X'pPڅ=tbY@K1᱀&bhBt5H )XN~I )D_, M5Q =eW \pA7h `,@ DK p, }@0  aC04&Sw|BX Y$A>P7ʔWn30H4&d @Bk/bQEp</ H/ >u:ɜ'Jb1>:RnD.f k~Bl"d&oL?,PEH Z D-Lp [&L )XV. )!F, *;@@@000 ```???pppPPPࠠߟoooOOO___cccKKKÇ dihlp,tmx|0+Ȥrl:ШtJZجvxL.zn^{N~qDygxYu8ۼ뀁='GLȰÇ#JHŋ3jȱR)H&%F0yҎJ/IL8s@@Gkxx0@A 9@ !A%0P Ki,AMZmAk-KwO@uWG_E,p@b(@",@F"i|viXpR\@1 j< PHAxCۯc8 lhu; I( Wn[:k 7+#<&/]bPl}3?FyiKYR mKAL~D4YJ.Їެm (P`[c@Q `+#(H& Dbj?kf@`)<!} piew/kFt0jVTS (n}@WtK-b=94 HsIUI HbXo$0Uxa:(PkHۂ5qmg X $ gY1Йi!" r{-֊B[[-Bݐ4z"a;5mO0εi.(F62u yI+#h`>~w+/bxgvESpA $2 1(VxR0gL w@"&$}`sP卌U @)+"\(CM8`,q@m5(vl<@h.{F_Ą1L;,Vt\^} t)lIZrk ,f0)t: K;/̐$PdB2S>&],c!T,iI01hRV5Kޔ%0,Z@|V!`Md,PYZxc-0`, H` 41KG)N @*LZsq~m` plMb@ Al[f^@X;G͋\ 4j@g%Ȼ+,5|EV^E#@О\]`CN`"`VxNmw.8 &?9m)>)-"LE@D6L&eM%;J{iہ{;95mLA" ){]d6j`;ӚD w3Xzl2p@ilK cJzrrs1:2XETmqUr$Ac(~p:$j2*:UjZ @g}$GMaN}r6P<&SD d0x0 |jr=E&7H5=q!1'0EE@c_5^I,yR3 Tmd0RC!b515%p(=>r=uU]eSL&D"?#Pe!9@ْ<>TE1M#tOhRHGT4X3kjw?1H\qr is1xr@;ȧ8exBBQBF$wLMA]f?]S K30KSa(((V@U.Rc 0,$"Os#~!-s$nx }/4l&A"OhVD40jiVY=1dya^!&cz&z#&!4"XQ80DƎ,c+A)@"94<$Hm.RL:8$ipؐXy}1dG91`C64;V:Kw;fV% M<ב[CA5Ma0/q.iW c_`&y|I1?^@Eٙp|ae#K$Tx 9+keyЛ9H!,1r@@@```000 PPPpppరϏ___???oooOOOcccKKKÇ #`hlp,tmx@>pH,Ȥrl:ШDNجvxL.tJRp~, m9 |7y 8 H'(( 0sAp 8/NG' C(  '(]+#7&M6(C(3Xڮ%Nȓ T@;  d4BL xQ$"/q&*d!Ch!`֤h&A. V0AO.HÒ)Zj⟊(@p ,(sʝKn6J8P@Y@@&؄ ( VfL`z}; at1 D4`  0`Ph@^ J\Zr8Kf@w՞P}Bu6WR!( 0zkOV5x" tBkI& 8`@ p_1026Z.6C;D S&0/2%] F}E)T_ [@  L9Р)mR# UIg&` Hb"A R@:?e&}BƭAX Uꪬq cIЭ.$@rBH@s.(' Гt-P  4 3(I0$A i+G)p9롧*2 `,; У@鋣tkH 'Fڻ+@,0Aٺ1'+›a=[&C [D)ȋi@@E o +!\"5W$8"2N}&G1 tO=AX5tc` H`ӎf׀ͬBȁ!Gň|> mc3d @`H066 .k׀,@T"}7@gNzp ?PneW"| x2.:;zApaC0fF G/(0 `,C1#F¬" ,@e`ـ+ْ@3U.~̠0?PI FGؙ' ۠ WB'ts-F즐8 s@>H(4#2PW!,1]@@@???000```߿OOOppp ooo𠠠PPP___ c`&lp,tmx|pH,DШtJZجv1ISwL.zn}K@{N{7}~qP" "  aT#N++ Oτn\M> "'b$GAO\ȰJH ā3j"/ CFȓ(S!,1e@@@```PPP ppp000???___///oooOOO b`'lp,tmx|pH,Ȥ@"˨tJZجvޚ'K.znx",6~ytu8a%uQtv%  U ' + +׫%-]#aPA#%   d"#3Ʉ)$jȱń:IGJK\IJ-cʜ9eH8sDΟ@!,1v@@@??? ppp000///___ooo```PPPﰰϯ #`f*p,tmx|pH,Ȥr#9c,sJZجvzA' mznPF# x0}a"c*Zwdf z /+g%ɤ4."%q H`}5 p@#J|987ǏXɓ(2L/Jʗ0cZiE2s-< ŧ,S@‹,&@s"Ͽ5",@CEwB[s v*$u򥣂{yFjfyBgՇ^(Vߊ,b` ᠤ twND =W.vx fj nM锛 $B8Xfe 1Ap NpDk TOA흢 gd(y PA/!B)@6bV6l)餔EA'TiHt m$ր&Wˑ,5Zh^ סz6>r*<`e[vByDld!z`@WtݶжH;&P-cцk 7/<WlOsw_! ,i1@@@ 000```???pppPPPЯ___ooo///` `&lp,tmx|pH,Ȥr<(TsJZجvz=znk\* t"1˻& ?~*" ,#,r_t"&R9+}* " , ,˕& 8*2#|+-.t ¢ E(X+@F +A+,p 0HW`,% dIy8sɳ2pN.,0l7 8`:xE(b$] $HH+WˉHEn'_* C)b87o/v` E` 2T30bhIn A *9Va})aQN#VVjpOtuB"HBUht~U Yy`G!'`&E&kfgQpx6 cvP%WT.Z751• 見 e(a4H]knnk_,p;N3'pf(_p Wl!,r1@@@```000 pppPPPﰰߏ௯???___` `&lp,tmx|pH,ȤrH(TsJZجvzs=znpTDbo%>}+" ,  *"+ q`s'f9,2#*w+",˖f8++vҥ" ,/7 h58 "6*2p\h@U,Q*`=*&a( " ,HIx @rɳ'$`p@Qkjh*e Tpu"RJ*%/xU7b @Im*,$H=aA`l5E֍DJ0 P@i8ȓ+_(S+1noSi m]f%>S"炷"}1Ͽ/%ФXu Ǎi` R+݀P#{ \֊0DhP^`PTg ݝ[vayvx&ڕ%bBlE0r:3"pT&R1! DŽbh Ꙋ |てByB eNEz5Fx"‹)>B /NX1 ],%W[-#܅q|XzWJ@[ ֆ߈$hb6s$fb5ys|F18YY,N^*@Œ'F)S"&yq@0;%@R 8`EhЙ TǏp\5v'j(P 6MOg) pHvH ?H`y3qYFn w@PP hs'fR"ք_'4&;eL3I*l lBbS xt 5NQpc\c+ k92l 7p1Wla!!,1@@@``` 000𐐐PPPppp???П___///oooOOO` `'lp,tmx|pH,Ȥr$PPtJZجvz:zn\gaR pW1˷?#," , *,pM$'ex/2 , 9~u.|",  qy&7,+" +٬,.*d3"T`H@ 0h LP hxp ׁ/L<*qbA$GRUsIJy@ JJD!^, @ 10 0UCXmn26m (@ A a-O8d(0AV``E#KL9J@vBT\.\~pPw8FX@@Zp* MiZr.7uW^μJ ٌ."[eN6mSg+Tp^_`CA( ( 0fDd5ܩw&^ }_IW7"}r(4Ґn%ș ᬄaT# Q[Yֶ=5` Z*`f [MT`)c$ 5[ P0X^=DЦ@`xHV=L0'dBf[6MTbfE2pā}lXZܡ]I3&zQ2  `kvv"4pv`Vkmh1yyYSyV64v^+vĻ ۇP[ƻop Gl;q)f Ǹq!lɔ! ,1@@@000``` ௯???PPPppp___oooϏ///` `&lp,tmx|pH,Ȥr$9Q(tJZجvz2' zznTq,Љ\ ~|*",#,p@# cQ9+{*" ,  ",S#8*a+- . ¢:`SA.0,4H(!^4@H 8)W LɳϟBX@@2tC L `@ DtB ڠWr"1Rh|[᫗ pǐ#k2@hQkUT,@+JGW7UN"$Xu% Ѐ+Z+_Μ 10י>X wFdѱX`^Օ+N/wEo|(!wUnvo1(^|pf{7Bh} 2A0P`acV5  ao.l9e\v%#4@'"9P6ѝ&'X5D(j,`e:KdWͤN DN<0XZ_F*T$ \ŕ[,A@yX ! g H'x*9me#H6K_kRV2Z $` M 覻ILc(}|k tц+;m'”.G,qs1gS!,1@@@000 ```PPPpppﰰ???___` `&lp,tmx|pH,Ȥr#P(tJZجvz*zznGq,Љ^B}|+" , a +  pMr'e/2#+v+",Fre8, +u" ,/*  b1H "*.ZbP +P2@<*$U0!,Hż,h "rɳ8|PG8w[@ePE dZ *E_L|unDwrpQc a>+^RxG\'s!g"k #!lÄ!.,1@@@```000 PPP𠠠аppp???ooo` `&lp,tmx|pH,ȤrIjP˨tJZجvzK.znG%@'r! =D  ,# +n8p"&P9~s2 + t,u,| #B&x-y*",Ӡ"*.+mƝs x"у* :` <*RexG"-؀P Ј˗0c0BG ` „~QK$ Ty"8Xu|`ybAV:5B3>W ]Q  X˷_K$3T<@Az<} Z ۤ%2)@p`x,'U\ TX۸0*RыuMuuyFB‹%7Z 1;Mx urт6PIYv]7iZpRR [o9wr mz:X?mFQ#+s")$Š!&2&wq6!P 4PAD|` LǍH(\;Cu$ # l䠄JIPh0pz4E3&4pc$)#h:j86F("$@Mv*up_h"zs @B:tXIliEfR8)E.Brnxjnk*s^c+7)'!{, 1*'@@@ 000```𠠠pppϿPPP???___OOOoooǷ@pH,Ȥrl:ШtJZجv˵owL.zn|ND}`b_~_ɖ zHn(JÇ#J@ 'jȱZ`,@`Ǔ(S\y(I0cʜIS˚8s9&@ 5sѣHf+ӧP[1JU,jzׯ`lٳhݚ۷B` "RrKA`"} oDp{Nb@AK.;dq :  ( `+ j'!Pal T"_.HpVУ~C; 8(B]Ȁ`!"о58si@lL`xpr @gF pAkPz\XD*GsBe&n ݍ8vHY$ (A3/I{J !w#RXAJ<\D +R1,7|B0e#PA9AV@T߇Y1ICX'YCheXVg:Z,QcJzp)2wg$= @S $o 1D D`^~rķy2Py fh4-B8ⷀ /yXצ)E~& lcv0ejɈ:պ]dP.F]*IT& C\rajUxB3Y1C ve]trc//E-J|B֒ t?ڪd`!y%{W)s,|9 (8i# iT"0Sl4S3uR~g\T4g'Hnпell-Giji6fzjCql@.U7_\Jw&PM * O5Q;;sailD,gCF/ SCvL#GXd4),[탟xZE " @"xN0IV1gl8`C(4@cHױי^" `FXE܉$ F4,oN_Gc4Lr x#8grOv2rK6Qit># ख& j&v KH `q6Bp`ԧ-uj$I ؐ4{`S7v5-6@1bsgOMZi|  i-P%KZѭ橍%UtK]U#%^+0^=([ kXY-%#/ S* ÷rlK\(}p: }t ! ,,3@@@ ```000𠠠pppPPPﰰ???___OOOoooǷڋ޼H扦ʶ L ĢL*̦ JԪjܮ N (8HXhx)9IYiy *:JZjz +;K[k{ ,N^n~/?O_o0 <0… :|1ĉ+Z1ƍ;z2ȑ$K<2ʕ,[| 3̙4kڼ3Ν<{ 4СD=4ҥL:} 5ԩTZ5֭\z 6رd˚=6ڵlۺ} 7ܹtڽ7޽| 8 8qbÌS*~8rNJ%[Iy3# :gKWm:uDԪ[3d:Aزk m;w?ܺ{;<‹#ndBIdFdJ.dN> eRNIeV^eZne^~ fbIfffjfn grIgvމgzg~ hJhh.h> iNJi^ini~ jJjjj kJkފkk lKll.l> mNKm^mnm~ nKn枋nn oKoދoo pLpp /p? qOLq_qoq r"Lr&r*r. s2Ls6ߌs:s> tBMtFtJ/tN? uROMuV_uZou^ vbMvfvjvn wrMwvߍwzw~ xNxx/x?yONy_yoyz袏Nz馟zꪯz뮿{N{ߎ{{|O||/|?}OO}_}o}~O~柏~~Oߏ p,*p lJp/ jp?p$, Op,l _p4 op< qD,qQ;landrun-0.1.15/go.mod000066400000000000000000000007151477432027300143630ustar00rootroot00000000000000module github.com/zouuup/landrun go 1.18 require ( github.com/landlock-lsm/go-landlock v0.0.0-20250303204525-1544bccde3a3 github.com/urfave/cli/v2 v2.27.6 ) require ( github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect golang.org/x/sys v0.26.0 // indirect kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 // indirect ) landrun-0.1.15/go.sum000066400000000000000000000025351477432027300144120ustar00rootroot00000000000000github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/landlock-lsm/go-landlock v0.0.0-20250303204525-1544bccde3a3 h1:zcMi8R8vP0WrrXlFMNUBpDy/ydo3sTnCcUPowq1XmSc= github.com/landlock-lsm/go-landlock v0.0.0-20250303204525-1544bccde3a3/go.mod h1:RSub3ourNF8Hf+swvw49Catm3s7HVf4hzdFxDUnEzdA= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 h1:HsB2G/rEQiYyo1bGoQqHZ/Bvd6x1rERQTNdPr1FyWjI= kernel.org/pub/linux/libs/security/libcap/psx v1.2.70/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= landrun-0.1.15/internal/000077500000000000000000000000001477432027300150665ustar00rootroot00000000000000landrun-0.1.15/internal/exec/000077500000000000000000000000001477432027300160125ustar00rootroot00000000000000landrun-0.1.15/internal/exec/runner.go000066400000000000000000000006301477432027300176510ustar00rootroot00000000000000package exec import ( "os/exec" "syscall" "github.com/zouuup/landrun/internal/log" ) func Run(args []string, env []string) error { binary, err := exec.LookPath(args[0]) if err != nil { return err } log.Info("Executing: %v", args) // Only pass the explicitly specified environment variables // If env is empty, no environment variables will be passed return syscall.Exec(binary, args, env) } landrun-0.1.15/internal/log/000077500000000000000000000000001477432027300156475ustar00rootroot00000000000000landrun-0.1.15/internal/log/log.go000066400000000000000000000022431477432027300167600ustar00rootroot00000000000000package log import ( "log" "os" "strings" ) type Level int const ( LevelError Level = iota LevelInfo LevelDebug ) var ( debug = log.New(os.Stderr, "[landrun:debug] ", log.LstdFlags) info = log.New(os.Stderr, "[landrun] ", log.LstdFlags) error = log.New(os.Stderr, "[landrun:error] ", log.LstdFlags) currentLevel = LevelInfo // default level ) // SetLevel sets the logging level func SetLevel(level string) { switch strings.ToLower(level) { case "error": currentLevel = LevelError case "info": currentLevel = LevelInfo case "debug": currentLevel = LevelDebug default: currentLevel = LevelError } } // Debug logs a debug message func Debug(format string, v ...interface{}) { if currentLevel >= LevelDebug { debug.Printf(format, v...) } } // Info logs an info message func Info(format string, v ...interface{}) { if currentLevel >= LevelInfo { info.Printf(format, v...) } } // Error logs an error message func Error(format string, v ...interface{}) { if currentLevel >= LevelError { error.Printf(format, v...) } } // Fatal logs an error message and exits func Fatal(format string, v ...interface{}) { error.Printf(format, v...) os.Exit(1) } landrun-0.1.15/internal/sandbox/000077500000000000000000000000001477432027300165245ustar00rootroot00000000000000landrun-0.1.15/internal/sandbox/sandbox.go000066400000000000000000000150551477432027300205170ustar00rootroot00000000000000package sandbox import ( "fmt" "os" "github.com/landlock-lsm/go-landlock/landlock" "github.com/landlock-lsm/go-landlock/landlock/syscall" "github.com/zouuup/landrun/internal/log" ) type Config struct { ReadOnlyPaths []string ReadWritePaths []string ReadOnlyExecutablePaths []string ReadWriteExecutablePaths []string BindTCPPorts []int ConnectTCPPorts []int BestEffort bool UnrestrictedFilesystem bool UnrestrictedNetwork bool } // getReadWriteExecutableRights returns a full set of permissions including execution func getReadWriteExecutableRights(dir bool) landlock.AccessFSSet { accessRights := landlock.AccessFSSet(0) accessRights |= landlock.AccessFSSet(syscall.AccessFSExecute) accessRights |= landlock.AccessFSSet(syscall.AccessFSReadFile) accessRights |= landlock.AccessFSSet(syscall.AccessFSWriteFile) accessRights |= landlock.AccessFSSet(syscall.AccessFSTruncate) accessRights |= landlock.AccessFSSet(syscall.AccessFSIoctlDev) if dir { accessRights |= landlock.AccessFSSet(syscall.AccessFSReadDir) accessRights |= landlock.AccessFSSet(syscall.AccessFSRemoveDir) accessRights |= landlock.AccessFSSet(syscall.AccessFSRemoveFile) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeChar) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeDir) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeReg) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeSock) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeFifo) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeBlock) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeSym) accessRights |= landlock.AccessFSSet(syscall.AccessFSRefer) } return accessRights } func getReadOnlyExecutableRights(dir bool) landlock.AccessFSSet { accessRights := landlock.AccessFSSet(0) accessRights |= landlock.AccessFSSet(syscall.AccessFSExecute) accessRights |= landlock.AccessFSSet(syscall.AccessFSReadFile) if dir { accessRights |= landlock.AccessFSSet(syscall.AccessFSReadDir) } return accessRights } // getReadOnlyRights returns permissions for read-only access func getReadOnlyRights(dir bool) landlock.AccessFSSet { accessRights := landlock.AccessFSSet(0) accessRights |= landlock.AccessFSSet(syscall.AccessFSReadFile) if dir { accessRights |= landlock.AccessFSSet(syscall.AccessFSReadDir) } return accessRights } // getReadWriteRights returns permissions for read-write access func getReadWriteRights(dir bool) landlock.AccessFSSet { accessRights := landlock.AccessFSSet(0) accessRights |= landlock.AccessFSSet(syscall.AccessFSReadFile) accessRights |= landlock.AccessFSSet(syscall.AccessFSWriteFile) accessRights |= landlock.AccessFSSet(syscall.AccessFSTruncate) accessRights |= landlock.AccessFSSet(syscall.AccessFSIoctlDev) if dir { accessRights |= landlock.AccessFSSet(syscall.AccessFSReadDir) accessRights |= landlock.AccessFSSet(syscall.AccessFSRemoveDir) accessRights |= landlock.AccessFSSet(syscall.AccessFSRemoveFile) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeChar) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeDir) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeReg) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeSock) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeFifo) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeBlock) accessRights |= landlock.AccessFSSet(syscall.AccessFSMakeSym) accessRights |= landlock.AccessFSSet(syscall.AccessFSRefer) } return accessRights } // isDirectory checks if the given path is a directory func isDirectory(path string) bool { fileInfo, err := os.Stat(path) if err != nil { return false } return fileInfo.IsDir() } func Apply(cfg Config) error { log.Info("Sandbox config: %+v", cfg) // Get the most advanced Landlock version available llCfg := landlock.V5 if cfg.BestEffort { llCfg = llCfg.BestEffort() } // Collect our rules var file_rules []landlock.Rule var net_rules []landlock.Rule // Process executable paths for _, path := range cfg.ReadOnlyExecutablePaths { log.Debug("Adding read-only executable path: %s", path) file_rules = append(file_rules, landlock.PathAccess(getReadOnlyExecutableRights(isDirectory(path)), path)) } for _, path := range cfg.ReadWriteExecutablePaths { log.Debug("Adding read-write executable path: %s", path) file_rules = append(file_rules, landlock.PathAccess(getReadWriteExecutableRights(isDirectory(path)), path)) } // Process read-only paths for _, path := range cfg.ReadOnlyPaths { log.Debug("Adding read-only path: %s", path) file_rules = append(file_rules, landlock.PathAccess(getReadOnlyRights(isDirectory(path)), path)) } // Process read-write paths for _, path := range cfg.ReadWritePaths { log.Debug("Adding read-write path: %s", path) file_rules = append(file_rules, landlock.PathAccess(getReadWriteRights(isDirectory(path)), path)) } // Add rules for TCP port binding for _, port := range cfg.BindTCPPorts { log.Debug("Adding TCP bind port: %d", port) net_rules = append(net_rules, landlock.BindTCP(uint16(port))) } // Add rules for TCP connections for _, port := range cfg.ConnectTCPPorts { log.Debug("Adding TCP connect port: %d", port) net_rules = append(net_rules, landlock.ConnectTCP(uint16(port))) } if cfg.UnrestrictedFilesystem && cfg.UnrestrictedNetwork { log.Info("Unrestricted filesystem and network access enabled; no rules applied.") return nil } if cfg.UnrestrictedFilesystem { log.Info("Unrestricted filesystem access enabled.") } if cfg.UnrestrictedNetwork { log.Info("Unrestricted network access enabled") } // If we have no rules, just return if len(file_rules) == 0 && len(net_rules) == 0 && !cfg.UnrestrictedFilesystem && !cfg.UnrestrictedNetwork { log.Error("No rules provided, applying default restrictive rules, this will restrict anything landlock can do.") err := llCfg.Restrict() if err != nil { return fmt.Errorf("failed to apply default Landlock restrictions: %w", err) } log.Info("Default restrictive Landlock rules applied successfully") return nil } // Apply all rules at once log.Debug("Applying Landlock restrictions") if !cfg.UnrestrictedFilesystem { err := llCfg.RestrictPaths(file_rules...) if err != nil { return fmt.Errorf("failed to apply Landlock filesystem restrictions: %w", err) } } if !cfg.UnrestrictedNetwork { err := llCfg.RestrictNet(net_rules...) if err != nil { return fmt.Errorf("failed to apply Landlock network restrictions: %w", err) } } log.Info("Landlock restrictions applied successfully") return nil } landrun-0.1.15/test.sh000077500000000000000000000236061477432027300145770ustar00rootroot00000000000000#!/bin/bash # Check if we should keep the binary KEEP_BINARY=false USE_SYSTEM_BINARY=false NO_BUILD=false while [ "$#" -gt 0 ]; do case "$1" in "--keep-binary") KEEP_BINARY=true shift ;; "--use-system") USE_SYSTEM_BINARY=true shift ;; "--no-build") NO_BUILD=true shift ;; *) echo "Unknown parameter: $1" exit 1 ;; esac done # Don't exit on error, we'll handle errors in the run_test function set +e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Function to print colored output print_status() { echo -e "${YELLOW}[TEST]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } # Build the binary if not using system binary if [ "$USE_SYSTEM_BINARY" = false ]; then if [ "$NO_BUILD" = false ]; then print_status "Building landrun binary..." go build -o landrun cmd/landrun/main.go if [ $? -ne 0 ]; then print_error "Failed to build landrun binary" exit 1 fi print_success "Binary built successfully" else print_success "Using already built landrun binary" fi fi # Create test directories TEST_DIR="test_env" RO_DIR="$TEST_DIR/ro" RO_DIR_NESTED_RO="$RO_DIR/ro_nested_ro_1" RO_DIR_NESTED_RW="$RO_DIR/ro_nested_rw_1" RO_DIR_NESTED_EXEC="$RO_DIR/ro_nested_exec" RW_DIR="$TEST_DIR/rw" RW_DIR_NESTED_RO="$RW_DIR/rw_nested_ro_1" RW_DIR_NESTED_RW="$RW_DIR/rw_nested_rw_1" RW_DIR_NESTED_EXEC="$RW_DIR/rw_nested_exec" EXEC_DIR="$TEST_DIR/exec" NESTED_DIR="$TEST_DIR/nested/path/deep" print_status "Setting up test environment..." rm -rf "$TEST_DIR" mkdir -p "$RO_DIR" "$RW_DIR" "$EXEC_DIR" "$NESTED_DIR" "$RO_DIR_NESTED_RO" "$RO_DIR_NESTED_RW" "$RO_DIR_NESTED_EXEC" "$RW_DIR_NESTED_RO" "$RW_DIR_NESTED_RW" "$RW_DIR_NESTED_EXEC" # Create test files echo "readonly content" > "$RO_DIR/test.txt" echo "readwrite content" > "$RW_DIR/test.txt" echo "nested content" > "$NESTED_DIR/test.txt" echo "#!/bin/bash" > "$EXEC_DIR/test.sh" echo "echo 'executable content'" >> "$EXEC_DIR/test.sh" chmod +x "$EXEC_DIR/test.sh" cp $EXEC_DIR/test.sh $EXEC_DIR/test2.sh cp "$RO_DIR/test.txt" "$RO_DIR_NESTED_RO/test.txt" cp "$RO_DIR/test.txt" "$RW_DIR_NESTED_RO/test.txt" cp "$RW_DIR/test.txt" "$RO_DIR_NESTED_RW/test.txt" cp "$RW_DIR/test.txt" "$RW_DIR_NESTED_RW/test.txt" cp "$EXEC_DIR/test.sh" "$RO_DIR_NESTED_EXEC/test.sh" cp "$EXEC_DIR/test.sh" "$RW_DIR_NESTED_EXEC/test.sh" cp "$EXEC_DIR/test.sh" "$RO_DIR_NESTED_RO/test.sh" cp "$EXEC_DIR/test.sh" "$RW_DIR_NESTED_RO/test.sh" cp "$EXEC_DIR/test.sh" "$RO_DIR_NESTED_RW/test.sh" cp "$EXEC_DIR/test.sh" "$RW_DIR_NESTED_RW/test.sh" # Create a script in RW dir to test execution in RW dirs echo "#!/bin/bash" > "$RW_DIR/rw_script.sh" echo "echo 'this script is in a read-write directory'" >> "$RW_DIR/rw_script.sh" chmod +x "$RW_DIR/rw_script.sh" # Function to run a test case run_test() { local name="$1" local cmd="$2" local expected_exit="$3" # Replace ./landrun with landrun if using system binary if [ "$USE_SYSTEM_BINARY" = true ]; then cmd="${cmd//.\/landrun/landrun}" fi print_status "Running test: $name" eval "$cmd" local exit_code=$? if [ $exit_code -eq $expected_exit ]; then print_success "Test passed: $name" return 0 else print_error "Test failed: $name (expected exit $expected_exit, got $exit_code)" exit 1 fi } # Test cases print_status "Starting test cases..." Basic access tests run_test "Read-only access to file" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --ro $RO_DIR -- cat $RO_DIR/test.txt" \ 0 run_test "Read-only access to nested file" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --ro $RO_DIR -- cat $RO_DIR_NESTED_RO/test.txt" \ 0 run_test "Write access to nested directory writable nested in read-only directory" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --ro $RO_DIR --rw $RO_DIR_NESTED_RW -- touch $RO_DIR_NESTED_RW/created_file" \ 0 run_test "Write access to nested file writable nested in read-only directory" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --ro $RO_DIR --rw $RO_DIR_NESTED_RW/created_file -- touch $RO_DIR_NESTED_RW/created_file" \ 0 run_test "Read-write access to file" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --ro $RO_DIR --rw $RW_DIR touch $RW_DIR/new.txt" \ 0 run_test "No write access to read-only directory" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --ro $RO_DIR --rw $RW_DIR touch $RO_DIR/new.txt" \ 1 # Executable permission tests run_test "Execute access with rox flag" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --rox $EXEC_DIR -- $EXEC_DIR/test.sh" \ 0 run_test "Execute access with rox flag on file" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --rox $EXEC_DIR/test.sh -- $EXEC_DIR/test.sh" \ 0 run_test "Execute access with rox flag on a file that is executable in same directory that one is allowed" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --rox $EXEC_DIR/test.sh -- $EXEC_DIR/test2.sh" \ 1 run_test "Execute a file with --add-exec flag" \ "./landrun --log-level debug --add-exec --rox /usr --ro /lib --ro /lib64 --rox $EXEC_DIR/test.sh -- $EXEC_DIR/test2.sh" \ 0 run_test "Execute a file with --add-exec and --ldd flag" \ "./landrun --log-level debug --add-exec --ldd -- /usr/bin/true" \ 0 run_test "No execute access with just ro flag" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --ro $EXEC_DIR -- $EXEC_DIR/test.sh" \ 1 run_test "Execute access in read-write directory" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --rwx $RW_DIR -- $RW_DIR/rw_script.sh" \ 0 run_test "No execute access in read-write directory without rwx" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --rw $RW_DIR -- $RW_DIR/rw_script.sh" \ 1 # Directory traversal tests run_test "Directory traversal with root access" \ "./landrun --log-level debug --rox / -- ls /usr" \ 0 run_test "Deep directory traversal" \ "./landrun --log-level debug --rox / -- ls $NESTED_DIR" \ 0 # Multiple paths and complex specifications run_test "Multiple read paths" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --ro $RO_DIR --ro $NESTED_DIR -- cat $NESTED_DIR/test.txt" \ 0 run_test "Comma-separated paths" \ "./landrun --log-level debug --rox /usr --ro /lib,/lib64,$RO_DIR -- cat $RO_DIR/test.txt" \ 0 # System command tests run_test "Simple system command" \ "./landrun --log-level debug --rox /usr --ro /etc -- whoami" \ 0 run_test "System command with arguments" \ "./landrun --log-level debug --rox / -- ls -la /usr/bin" \ 0 # Edge cases run_test "Non-existent read-only path" \ "./landrun --log-level debug --ro /usr --ro /lib --ro /lib64 --ro /nonexistent/path -- ls" \ 1 run_test "No configuration" \ "./landrun --log-level debug -- ls /" \ 1 # Process creation and redirection tests run_test "Process creation with pipe" \ "./landrun --log-level debug --rox / -- bash -c 'ls /usr | grep bin'" \ 0 run_test "File redirection" \ "./landrun --log-level debug --rox / --rw $RW_DIR -- bash -c 'ls /usr > $RW_DIR/output.txt && cat $RW_DIR/output.txt'" \ 0 # Network restrictions tests (if kernel supports it) run_test "TCP connection without permission" \ "./landrun --log-level debug --rox /usr --ro / -- curl -s --connect-timeout 2 https://example.com" \ 7 run_test "TCP connection with permission" \ "./landrun --log-level debug --rox /usr --ro / --connect-tcp 443 -- curl -s --connect-timeout 2 https://example.com" \ 0 # Environment isolation tests export TEST_ENV_VAR="test_value_123" run_test "Environment isolation" \ "./landrun --log-level debug --rox /usr --ro / -- bash -c 'echo \$TEST_ENV_VAR'" \ 0 run_test "Environment isolation (no variables should be passed)" \ "./landrun --log-level debug --rox /usr --ro / -- bash -c '[[ -z \$TEST_ENV_VAR ]] && echo \"No env var\" || echo \$TEST_ENV_VAR'" \ 0 run_test "Passing specific environment variable" \ "./landrun --log-level debug --rox /usr --ro / --env TEST_ENV_VAR -- bash -c 'echo \$TEST_ENV_VAR | grep \"test_value_123\"'" \ 0 run_test "Passing custom environment variable" \ "./landrun --log-level debug --rox /usr --ro / --env CUSTOM_VAR=custom_value -- bash -c 'echo \$CUSTOM_VAR | grep \"custom_value\"'" \ 0 # Combining different permission types run_test "Mixed permissions" \ "./landrun --log-level debug --rox /usr --ro /lib --ro /lib64 --rox $EXEC_DIR --rwx $RW_DIR -- bash -c '$EXEC_DIR/test.sh > $RW_DIR/output.txt && cat $RW_DIR/output.txt'" \ 0 # Specific regression tests for bugs we fixed run_test "Root path traversal regression test" \ "./landrun --log-level debug --rox /usr -- /usr/bin/ls /usr" \ 0 run_test "Execute from read-only paths regression test" \ "./landrun --log-level debug --rox /usr --ro /usr/bin -- /usr/bin/id" \ 0 run_test "Unrestricted filesystem access" \ "./landrun --log-level debug --unrestricted-filesystem ls /usr" \ 0 run_test "Unrestricted network access" \ "./landrun --log-level debug --unrestricted-network --rox /usr --ro /etc -- curl -s --connect-timeout 2 https://example.com" \ 0 run_test "Restricted filesystem access" \ "./landrun --log-level debug ls /usr" \ 1 run_test "Restricted network access" \ "./landrun --log-level debug --rox /usr --ro /etc -- curl -s --connect-timeout 2 https://example.com" \ 7 # Cleanup print_status "Cleaning up..." rm -rf "$TEST_DIR" if [ "$KEEP_BINARY" = false ] && [ "$USE_SYSTEM_BINARY" = false ]; then rm -f landrun fi print_success "All tests completed!"