Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

garywill/bblsandbox

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

61 Commits
 
 
 
 
 
 
 
 

Repository files navigation

English | 中文

Box-in-box Linux Sandbox

A Linux sandbox tool that allows unlimited nesting. (BBL, short for Box-in-box Linux).

Early-stage project — free to try and read the code. Note: runtime strings and code comments are currently not in English.

Overview

This tool focus on a smooth sub-namespaces nesting experience. You can create a tree of layer-on-layer containers as you like.

You can run "untrusted" and "semi-trusted" processes in different layers of one sandbox.

Every layer's isolation degree is configurable. Every layer's filesystem accessibility range is fine-grained controllable. Arbitrary nestable.

Here's an example, the sandbox container tree might look like:

[Linux Host]
    [X11]   Real Desktop
    [dbus-daemon --session]  <A>    Real user dbus service

    [BBL Sandbox]
      |-[Sub-container : Untrusted zone]
      |   |
      |   |-[Sub-container : Untrusted : User App]
      |   |     [USER-APPS RUN HERE]
      |   | 
      |   |-[Sub-container : Untrusted : Companion Processes (Group 2)]
      |         [Xpra X server]      Isolated X11 Server
      |         [dbus-proxy]  <C>    Splits and forwards user dbus to internal D and external B
      |         [dbus-daemon --session]  <D>  Internal user dbus service
      |         [dbus-daemon --system]    Internal system-level dbus
      |         [keyring daemon]          Internal Keyring Service
      |         [icewm]            Lightweight Window Manager, usually paired with Xephyr
      |         
      |-[Sub-container : Semi-trusted zone : Companion Processes (Group 11)]
                [Xephyr]           Isolated X11 Server + Client
                [Xpra client]      Seamless Isolated X11 Client
                [dbus-proxy]  <B>  Filters and forwards dbus, relaying between A and C

(Usually not all above will be started. It depends on user options)

In above example, two sub-containers are for companion processes. Difference is that the "semi-trusted" one can access host's X11 and DBus socket, while the "untrusted" one can't.

Why made this? What about its security?

I call it a Firejail/Flatpak alternative. Firejail/Bubblewrap and even official tool unshare don't expose some low-level knobs I want. So I built this fully controllable tool. Nesting to arbitrary depth and convinient container tree configuring are our main feature, which other tools don't provide.

Early-stage. It works and you can read the code, but it has not been developed or audited by a security team.

Features and Implementation Status

  • No root needed. No daemon. No host cap/suid needed.
  • No traces in home or disk. Temp data in /tmp deleted automatically
  • Image-free: no container images to download like Docker/LXC. Reuse the host system so tools such as vim/git don’t need to be reinstalled inside
  • Fully customizable nested namespaces
    • Per-layer PID/mount/... ns controls
    • Per-layer new rootfs and fine-grained control over filesystem path setup
      • Bind mount (rw/ro)
      • Directory overlay
      • Creation or temporary override of files (rw/ro); tmpfs directories (rw/ro)
      • Symlink
    • Environment variable control inside the sandbox
    • UID=0 in layer1, back to uid=1000 in last layer; drop caps; no_new_privs
  • Mount AppImage internally
  • Use GUI in sandbox:
    • Optional host X11 exposure to sandbox
    • Optional isolated X11 with Xephyr
    • Optional Xpra seamless X11 proxy
    • Optional host Wayland exposure to sandbox
    • Optional isolated full desktop in isolated GUI
  • Optional access to real hardware or just GPU
  • DBus:
    • Optional host DBus exposure to sandbox
    • Optional DBus proxy filtering DBus communication
  • Optional Seccomp
  • Optional network traffic control
  • Instance Manage and Args Passing
    • Multi-instances for same sandbox (Multiple startups of same sandbox will have multi-instances running. Each other isolated)
    • Single-instance for same sandbox (Multiple startups of same sandbox will send args to the first-started running instance)
  • In-container shell socket exposed to host
  • Single-file script. Copy as you like, edit options at file head and run. No install. Minimal dependencies.

Dependencies

Required:

  • Linux kernel >= 6.3 (with unprivileged user namespace support)
  • glibc
  • Python >= 3.12
  • bash

(Python script although it is, no dependencies on third-party libraries. It uses libc to talk to Linux kernel)

Optional (for extra features):

  • squashfuse (mount AppImage internally)
  • Xephyr (isolated X11)

Simple usage examples

In following examples, app processes in sandbox can see only ro system dirs, empty home, and some paths/sockets that user explictly allows.

Example 1 — Run AppImage in sandbox

Place a copy of BBL script next to an AppImage of some app you downloaded.

/anyhdd/freecad/bblsbxrun_freecad.py
/anyhdd/freecad/FreeCAD.AppImage
/anyhdd2/projects_save/

Edit .py file and config like this:

sandbox_name='freecad',
user_mnts = [
    d(mttype='appimage', appname='freecad', src=f'{si.startdir_on_host}/FreeCAD.AppImage'),
    d(mttype='bind', src='/anyhdd2/projects_save/', SDS=1),
],
gui="realX",

BBL mounts AppImage contents inside the sandbox so AppImage itself doesn’t need to have FUSE caps. This mounts the AppImage under /sbxdir/apps/freecad/ inside the sandbox. After launching the sandbox, run /sbxdir/apps/run_freecad inside it to start the app.

Project files created by the app can be saved under /anyhdd2/projects_save/ because that host path was bound into the sandbox. The SDS flag means “source and destination are the same” so the directory appears with the same path inside and outside the sandbox.

Example 2 — running a downloaded binary

If you downloaded an app (for example firefox.tar.xz) and want to use the app inside the sandbox:

/anyhdd/ffx/bblsbxrun_firefox.py
/anyhdd/ffx/firefox/.... (contains firefox binaries and libraries)

Configure:

sandbox_name='firefox', # sandbox name
user_mnts = [
    d(mttype='robind', src=f'{si.startdir_on_host}/firefox', SDS=1), 
    # alternatively, remove SDS and set dest='/sbxdir/apps/firefox'
],
gui="realX",
dbus_session="allow", # input methods and other components need dbus

If you want to persist the browser profile, provide a fake home directory next to the script:

/anyhdd/ffx/bblsbxrun_firefox.py
/anyhdd/ffx/fakehome
/anyhdd/ffx/firefox/....

and configure:

homedir=f'{si.startdir_on_host}/fakehome',

The fakehome directory will be mounted into the sandbox at the user’s home path.

Example 3— use your existing vimrc inside the sandbox

user_mnts = [
    d(mttype='robind', src=f'{si.HOME}/.vimrc', SDS=1), 
],

Sandbox layering model

BBL is a multi-layer, nestable sandbox. The script ships with a default nested template:

Linux Host 
  |
 layer1 (management layer; PID isolation; start internal privilege)
  |
 layer2 (semi-trusted zone: mount ns isolation; user global privacy paths masked)
   |
   |--layer2a (drop caps; for trusted companion programs, like xpra client / dbus proxy)
   |
 layer2h (intermediary)
    |
  layer3 (untrusted zone: isolates most namespaces; sees system base paths; only data paths explicitly mounted by user are visible)
    |
    |--layer4 (drop caps; where user apps run)
    |--layer4a (drop caps; for untrusted companion programs, such as xpra server)

(layer2a and layer4a are both for companion programs. layer2a can access real X11 and real DBus, while layer4a not).

Normal users do not need to edit the default template — only tweak the user options section.

When the sandbox is started, user app or an interactive user shell (if requested) will usually run at layer4.

This project is early-stage and the design may change.

A compact template looks like: (for advanced users)

layer1 = d( # layer 1
    layer_name='layer1', # do not change the default layer_name
    unshare_pid=True, unshare_user=True, ......
    
    sublayers = [
        d( # layer 2
            layer_name='layer2', # do not change the default layer_name
            unshare_pid=True, unshare_mnt=True, ....
            newrootfs=True, fs=[ ..... ], ....
            
            sublayers = [
                d( layer_name='layer2a', .... ),
                d( 
                    layer_name='layer2h', 
                    sublayers = [
                        d( layer_name='layer3', ..... , newrootfs=True, fs=[ ..... ], .....
                            sublayers=[ # layer 4
                                d( layer_name='layer4', .....  , user_shell=True ),
                                d( layer_name='layer4a', ..... ),
                            ],
                        ),
                    ] 
                )
            ],
        )
    ],
)

This is only a rough sketch of the default template. For details open the code.

Startup sequence

Each layer follows this basic flow:

  1. Load the layer configuration
  2. Call unshare() according to the layer configuration
  3. fork() — the following steps run in the child
  4. Temporary privileges escalation or dropping if configured (Write /proc/self/uid_map and related files as required)
  5. Build and mount the layer’s new rootfs (if configured)
  6. pivot_root into the new rootfs (if configured)
  7. Apply configured environment variable changes (if configured)
  8. Drop privileges (if configured)
  9. Launch a user shell, start sublayers, or run application(s), depending on configuration

The project is early-stage and the implementation may evolve.

Filesystem view inside the sandbox

A typical untrusted app’s visible filesystem inside the sandbox is assembled from plan entries like:

// # system directories read-only from the host
{'plan': 'robind', 'dest': '/bin', 'src': '/bin'}
{'plan': 'robind', 'dest': '/etc', 'src': '/etc'}
{'plan': 'robind', 'dest': '/lib64', 'src': '/lib64'}
.....

// # minimal /dev
{'plan': 'rotmpfs', 'dest': '/dev'}
{'plan': 'bind', 'dest': '/dev/console', 'src': '/dev/console'}
{'plan': 'bind', 'dest': '/dev/null', 'src': '/dev/null'}
{'plan': 'bind', 'dest': '/dev/random', 'src': '/dev/random'}
{'plan': 'devpts', 'dest': '/dev/pts'}
{'plan': 'tmpfs', 'dest': '/dev/shm'}
......

// # temporary writable directories
{'plan': 'tmpfs', 'dest': '/home/username'}
{'plan': 'tmpfs', 'dest': '/run'}
{'plan': 'tmpfs', 'dest': '/run/user/1000'}
{'plan': 'tmpfs', 'dest': '/tmp'}
......

// # user-configured mounts
{'plan': 'appimg-mount', 'src': '/anyhdd/freecad/FreeCAD.AppImage', 'dest': '/sbxdir/apps/freecad'}
{'plan': 'robind', 'src': '/anyhdd/ffx/firefox', 'dest': '/sbxdir/apps/firefox'}
{'plan': 'robind', 'dest': '/tmp/.X11-unix/X0', 'src': '/tmp/.X11-unix/X0'}
{'plan': 'robind', 'dest': '/tmp/dbus_session_socket', 'src': '/run/user/1000/bus'}

// # sandbox configuration directory
{'batch_plan': 'sbxdir-in-newrootfs', 'dest': '/sbxdir'}

(These plan entries are included in the default template so users usually don't have to create them manually.)

The /sbxdir directory contains:

  • AppImage mountpoints (users may need to know about)
  • Configuration and metadata for the current layer and its sublayers
  • Files used for communication with layer1 and the host
  • Scripts used to start sublayers
  • Mountpoints for sublayers’ new rootfs

How to edit the layer nesting template

TBD

About

Linux sandbox tool that allows unlimited nesting. Create container tree as you like

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.