Qore Programming Language Reference Manual 2.3.0
Loading...
Searching...
No Matches
Sandboxing and Security

Qore provides comprehensive sandboxing capabilities for safely executing untrusted or semi-trusted code. This is essential for applications that need to run user-provided code, such as:

  • Multi-tenant SaaS platforms
  • Plugin and extension systems
  • Online code execution environments
  • Web application scripting engines
  • Workflow and automation systems

Qore offers two complementary approaches to sandboxing:

  1. **Parse Options** - Binary on/off restrictions that completely disable categories of functionality
  2. **SandboxManager** - Fine-grained, configurable restrictions with allow/deny lists and resource limits

Parse Option Restrictions

Parse options provide a simple way to completely disable access to certain system capabilities. These are enforced at parse time, meaning code that attempts to use restricted features will fail to parse rather than failing at runtime.

Overview of Security Parse Options

The following parse options restrict system access:

| Parse Option | Description | |--------------|-------------| | PO_NO_FILESYSTEM | Disables all filesystem access | | PO_NO_NETWORK | Disables all network access | | PO_NO_THREAD_CONTROL | Disables thread creation and control | | PO_NO_THREAD_CLASSES | Disables thread synchronization classes | | PO_NO_THREADS | Combines PO_NO_THREAD_CONTROL and PO_NO_THREAD_CLASSES | | PO_NO_PROCESS_CONTROL | Disables process control (exit, fork, etc.) | | PO_NO_EXTERNAL_PROCESS | Disables spawning external processes | | PO_NO_EXTERNAL_INFO | Disables access to external system information | | PO_NO_EXTERNAL_ACCESS | Combines multiple restrictions for external access | | PO_NO_DATABASE | Disables database access | | PO_NO_MODULES | Disables loading modules | | PO_NO_IO | Disables all I/O operations | | PO_NO_GUI | Disables GUI functionality | | PO_NO_UNCONTROLLED_APIS | Disables APIs that could bypass sandboxing |

Using Parse Options

Parse options can be set when creating a Program object:

%new-style
%require-types
%strict-args
# Create a program with no filesystem or network access
Program p(PO_NEW_STYLE | PO_NO_FILESYSTEM | PO_NO_NETWORK);
# Parse user code - any filesystem/network calls will cause parse errors
try {
p.parse(user_code, "user_script");
} catch (hash<ExceptionInfo> ex) {
printf("Parse error: %s\n", ex.desc);
}

Parse options can also be set via parse directives in the code itself:

%no-filesystem
%no-network
# This code cannot access files or network

Or via command-line options:

qore --no-filesystem --no-network script.q

Lockdown Parse Option

The PO_LOCKDOWN parse option combines many restrictions for maximum security:

# PO_LOCKDOWN includes:
# - PO_NO_PROCESS_CONTROL
# - PO_NO_THREAD_CONTROL
# - PO_NO_TOP_LEVEL_STATEMENTS
# - PO_NO_GLOBAL_VARS
# - PO_NO_SUBROUTINE_DEFS
# - PO_NO_EXTERNAL_ACCESS (which includes PO_NO_FILESYSTEM, PO_NO_NETWORK, etc.)
Program p(PO_LOCKDOWN);

Parse Option Limitations

While parse options are effective for completely disabling functionality, they have limitations:

  • **Binary**: Cannot allow partial access (e.g., allow some files but not others)
  • **No resource limits**: Cannot limit memory, CPU time, or thread count
  • **No interruption**: Cannot stop runaway code (infinite loops)
  • **No SSRF prevention**: Cannot block specific IP ranges

For these advanced use cases, use the SandboxManager class.

SandboxManager Class

The SandboxManager class provides fine-grained, configurable sandboxing with support for:

Basic Usage

%new-style
%require-types
%strict-args
# Create and configure sandbox
SandboxManager sm();
sm.setMemoryLimit(100 * 1024 * 1024); # 100MB memory limit
sm.setWallTimeLimit(30000); # 30 second timeout
sm.setMaxThreads(4); # Max 4 threads
sm.blockPrivateNetworks(); # SSRF prevention
sm.setFilesystemDefaultPolicy(False); # Deny filesystem by default
# Create sandboxed program
Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
p.setSandboxManager(sm);
# Parse and run untrusted code
p.parse(user_code, "user_script");
auto result = p.callFunction("main");

Filesystem Security

The filesystem security system controls which paths can be accessed by sandboxed code. It supports:

  • **Sandbox root**: Restrict all access to within a specific directory (chroot-like)
  • **Allow/deny lists**: Fine-grained path-based access control
  • **Access modes**: Control read, write, execute, delete, and create permissions separately

Parse Options vs SandboxManager

| Feature | PO_NO_FILESYSTEM | SandboxManager | |---------|------------------|----------------| | Block all filesystem access | Yes | Yes (default deny policy) | | Allow specific directories | No | Yes | | Deny specific directories | No | Yes | | Different read/write permissions | No | Yes | | Sandbox root (chroot-like) | No | Yes |

Sandbox Root

Setting a sandbox root restricts all filesystem access to within that directory:

SandboxManager sm();
sm.setFilesystemSandboxRoot("/home/sandbox/userdata");
# Now all filesystem operations are restricted to /home/sandbox/userdata
# Attempts to access paths outside will raise FILESYSTEM-ACCESS-DENIED

The sandbox root:

  • Automatically resolves symlinks to prevent escapes
  • Blocks path traversal attacks (../)
  • Uses realpath() for canonical path resolution

Allow and Deny Lists

For more granular control, use allow and deny lists:

SandboxManager sm();
# Allow read access to data directory
sm.addFilesystemAllowedPath("/data/public", QSEC_READ);
# Allow read/write to uploads directory
sm.addFilesystemAllowedPath("/data/uploads", QSEC_READ | QSEC_WRITE | QSEC_CREATE);
# Deny access to secrets (takes precedence over allow)
sm.addFilesystemDeniedPath("/data/uploads/secrets");
Note
Denied paths always take precedence over allowed paths.

Access Mode Constants

The following constants control filesystem access modes:

| Constant | Value | Description | |----------|-------|-------------| | QSEC_READ | 1 | Read access to files | | QSEC_WRITE | 2 | Write/modify access to files | | QSEC_EXECUTE | 4 | Execute access to files | | QSEC_DELETE | 8 | Delete files | | QSEC_CREATE | 16 | Create new files | | QSEC_ALL | 31 | All access modes combined |

Default Policy

By default, unlisted paths are denied. You can change this behavior:

# Default: deny unlisted paths (most secure)
sm.setFilesystemDefaultPolicy(False);
# Alternative: allow unlisted paths, only deny explicitly listed
sm.setFilesystemDefaultPolicy(True);

Network Security

The network security system controls socket connections and provides SSRF (Server-Side Request Forgery) prevention.

Parse Options vs SandboxManager

| Feature | PO_NO_NETWORK | SandboxManager | |---------|---------------|----------------| | Block all network access | Yes | Yes (default deny policy) | | Allow specific hosts | No | Yes | | Allow specific IP ranges | No | Yes | | Block private networks (SSRF) | No | Yes | | Port restrictions | No | Yes | | Protocol filtering (TCP/UDP/UNIX) | No | Yes |

SSRF Prevention

Server-Side Request Forgery (SSRF) attacks trick applications into making requests to internal resources. The blockPrivateNetworks() method blocks all private and internal network ranges:

SandboxManager sm();
# Block all private networks (essential for SSRF prevention)
sm.blockPrivateNetworks();
# This blocks:
# - 127.0.0.0/8 (localhost)
# - 10.0.0.0/8 (RFC 1918 Class A)
# - 172.16.0.0/12 (RFC 1918 Class B)
# - 192.168.0.0/16 (RFC 1918 Class C)
# - 169.254.0.0/16 (link-local, includes cloud metadata 169.254.169.254)
# - ::1/128 (IPv6 localhost)
# - fe80::/10 (IPv6 link-local)
# - fc00::/7 (IPv6 unique local)
# Allow public internet access
sm.setNetworkDefaultPolicy(True);
Note
Security checks occur AFTER DNS resolution to prevent attacks using hostnames that resolve to internal IP addresses.

Host and IP Restrictions

Control access by hostname patterns and IP ranges:

SandboxManager sm();
# Allow specific hosts (supports wildcards)
sm.addNetworkAllowedHost("api.example.com");
sm.addNetworkAllowedHost("*.trusted-domain.org");
# Allow specific IP ranges (CIDR notation)
sm.addNetworkAllowedIPRange("203.0.113.0/24"); # IPv4
sm.addNetworkAllowedIPRange("2001:db8::/32"); # IPv6
# Deny specific ranges (takes precedence over allow)
sm.addNetworkDeniedIPRange("192.168.0.0/16");

Port Restrictions

Control which ports can be connected to:

SandboxManager sm();
# Allow only HTTPS (convenience method)
sm.allowHTTPSOnly();
# Or configure manually
sm.addNetworkAllowedPort(443, QSEC_NET_TCP); # HTTPS
sm.addNetworkAllowedPort(80, QSEC_NET_TCP); # HTTP
sm.addNetworkAllowedPort(53, QSEC_NET_ALL); # DNS (TCP and UDP)
sm.addNetworkAllowedPortRange(8000, 9000, QSEC_NET_TCP); # Custom range

Protocol Constants

The following constants control network protocols:

| Constant | Value | Description | |----------|-------|-------------| | QSEC_NET_TCP | 1 | TCP protocol | | QSEC_NET_UDP | 2 | UDP protocol | | QSEC_NET_UNIX | 4 | UNIX domain sockets | | QSEC_NET_ALL | 7 | All protocols |

Resource Limits

Resource limits prevent denial-of-service attacks and runaway code. Unlike parse options, SandboxManager can limit resources rather than completely disable features.

SandboxManager sm();
# Memory limit (bytes) - prevents memory exhaustion
sm.setMemoryLimit(100 * 1024 * 1024); # 100MB
# CPU time limit (milliseconds) - prevents CPU-bound infinite loops
sm.setCPUTimeLimit(10000); # 10 seconds of CPU time
# Wall clock time limit (milliseconds) - prevents I/O-bound hangs
sm.setWallTimeLimit(30000); # 30 seconds total
# Thread limit - prevents fork bombs
sm.setMaxThreads(4);
# Recursion depth limit - prevents stack overflow
sm.setMaxRecursionDepth(100);

When limits are exceeded, appropriate exceptions are raised:

| Exception | Cause | |-----------|-------| | SANDBOX-MEMORY-LIMIT | Memory limit exceeded | | SANDBOX-TIMEOUT | CPU or wall time limit exceeded | | SANDBOX-THREAD-LIMIT | Thread limit exceeded | | SANDBOX-RECURSION-LIMIT | Recursion depth exceeded |

Safe Code Interruption

The interrupt mechanism allows safely stopping sandboxed code that is stuck in loops or blocking operations. This is unique to SandboxManager - parse options cannot stop running code.

SandboxManager sm();
Program p(PO_NEW_STYLE);
p.setSandboxManager(sm);
p.parse(untrusted_code, "user_script");
# Start execution in background thread
Counter done(1);
background sub() {
try {
p.callFunction("main");
} catch (hash<ExceptionInfo> ex) {
# Will catch PROGRAM-INTERRUPTED
}
done.dec();
}();
# If execution takes too long, interrupt it
usleep(5s);
if (done.getCount() > 0) {
sm.requestInterrupt();
}
# Wait for completion
done.waitForZero();
# Clear interrupt for reuse
sm.clearInterrupt();

Interrupt Check Points

The interrupt mechanism is checked at:

  • **Loop boundaries**: while, for, foreach, do-while (checked before each iteration)
  • **Sleep functions**: sleep(), usleep() (polled every 100ms)
  • **Lock acquisition**: Mutex::lock(), RWLock::readLock(), RWLock::writeLock() (polled every 500ms)
  • **Auto-lock constructors**: AutoLock, AutoReadLock, AutoWriteLock (polled every 500ms)
  • **Queue operations**: Queue::get(), Queue::pop() (polled every 500ms)
  • **Thread synchronization**: Counter::waitForZero(), Condition::wait(), Gate::enter() (polled every 500ms)
  • **File I/O**: File read, write, and lock operations (polled at I/O poll interval)
  • **Stream I/O**: PipeInputStream::read(), PipeOutputStream::write() (polled at I/O poll interval)
  • **Standard I/O**: StdoutOutputStream::write(), StderrOutputStream::write() (polled at I/O poll interval)
  • **Database operations**: Datasource transaction lock acquisition (polled at I/O poll interval)

When interrupted, a PROGRAM-INTERRUPTED exception is raised. For external process functions (system(), backquote(), and the backquote operator), a SandboxManager also starts the child in a new process group so that requestInterrupt() can terminate the entire process group. Without a SandboxManager, children remain in the current process group and receive terminal signals (for example, Ctrl-C) along with the parent.

Polling Mechanism

For blocking operations, the interrupt mechanism uses a polling approach where the operation is performed with a timeout, then the interrupt status is checked, and the operation is retried if no interrupt was requested. This ensures that blocked threads can be interrupted even when waiting on locks or I/O operations.

| Operation Type | Polling Interval | Latency | |----------------|------------------|---------| | sleep(), usleep() | 100ms | Fast response for timed waits | | Threading primitives | 500ms | Lower overhead for lock contention | | I/O operations | 500ms | Consistent with threading primitives |

Note
The polling mechanism only incurs overhead when a SandboxManager is attached to the Program. Normal programs without a SandboxManager have zero polling overhead.

Security Presets

Two convenience presets are available for common use cases:

Lockdown Mode

Maximum security for completely untrusted code:

SandboxManager sm = SandboxManager::createLockdown();
# - No filesystem access
# - No network access
# - Memory limit: 10MB
# - CPU time: 1 second
# - Wall time: 5 seconds
# - Max threads: 1
# - Max recursion: 100

Web-Safe Mode

Suitable for web application sandboxing where some network access is needed:

SandboxManager sm = SandboxManager::createWebSafe();
# - No filesystem access
# - Network allowed but private networks blocked (SSRF prevention)
# - Memory limit: 100MB
# - CPU time: 10 seconds
# - Wall time: 30 seconds
# - Max threads: 4
# - Max recursion: 200

Combining Parse Options and SandboxManager

Parse options and SandboxManager can be used together for defense in depth:

# Use parse options to disable features entirely
Program p(PO_NEW_STYLE | PO_NO_EXTERNAL_PROCESS | PO_NO_DATABASE);
# Use SandboxManager for fine-grained control
SandboxManager sm();
sm.addFilesystemAllowedPath("/data/uploads", QSEC_READ | QSEC_WRITE);
sm.blockPrivateNetworks();
sm.setNetworkDefaultPolicy(True);
sm.setMemoryLimit(50 * 1024 * 1024);
sm.setWallTimeLimit(10000);
p.setSandboxManager(sm);

Configuration Inspection

You can inspect the current sandbox configuration:

SandboxManager sm();
sm.blockPrivateNetworks();
sm.setMemoryLimit(100 * 1024 * 1024);
# Get full configuration
hash<auto> config = sm.getConfiguration();
printf("Config: %N\n", config);
# Get filesystem-specific configuration
hash<auto> fs_config = sm.getFilesystemConfiguration();
# Get network-specific configuration
hash<auto> net_config = sm.getNetworkConfiguration();

Security Exceptions

The following exceptions may be raised by the sandbox system:

| Exception | Source | Description | |-----------|--------|-------------| | FILESYSTEM-ACCESS-DENIED | SandboxManager | Filesystem access blocked by security policy | | NETWORK-ACCESS-DENIED | SandboxManager | Network connection blocked by security policy | | PROGRAM-INTERRUPTED | SandboxManager | Execution interrupted via requestInterrupt() | | SANDBOX-MEMORY-LIMIT | SandboxManager | Memory limit exceeded | | SANDBOX-TIMEOUT | SandboxManager | CPU or wall time limit exceeded | | SANDBOX-THREAD-LIMIT | SandboxManager | Thread limit exceeded | | SANDBOX-RECURSION-LIMIT | SandboxManager | Recursion depth exceeded | | Parse error | Parse Options | Code uses disabled functionality |

Best Practices

  1. **Defense in depth**: Combine parse options with SandboxManager
    • Use parse options to completely disable unneeded features
    • Use SandboxManager for fine-grained control of allowed features
  2. **Always use blockPrivateNetworks()** for web-facing applications
    • Essential for SSRF prevention
    • Blocks access to cloud metadata endpoints (169.254.169.254)
    • Blocks access to internal services
  3. **Set reasonable resource limits**
    • Memory limits prevent memory exhaustion
    • Time limits prevent infinite loops
    • Thread limits prevent fork bombs
  4. **Use deny-by-default policies**
    • Only explicitly allow what is needed
    • Denied paths/IPs take precedence over allowed
  5. **Implement interrupt handling** for long-running operations
    • Use background threads with timeout monitoring
    • Call requestInterrupt() if execution exceeds expected time
  6. **Validate paths before adding to allow lists**
    • Use absolute canonical paths
    • Be aware of symlink escapes
    • Test with edge cases
  7. **Use lockdown mode for pure computation**
    • When code doesn't need external access, use createLockdown()
    • Provides maximum security with minimal configuration

Security Checklist

Before deploying sandboxed code execution:

  • [ ] Define maximum resource limits (memory, CPU, wall time, threads)
  • [ ] Block private networks if any network access is allowed
  • [ ] Use deny-by-default for filesystem access
  • [ ] Implement timeout monitoring with interrupt handling
  • [ ] Disable unneeded features with parse options
  • [ ] Test with malicious input (infinite loops, memory bombs, SSRF attempts)
  • [ ] Log security exceptions for monitoring
See also
SandboxManager
Program
PO_LOCKDOWN
PO_NO_EXTERNAL_ACCESS
Since
Qore 2.3 (SandboxManager class)