–safe Flag Bypass with readfile()/writefile() in SQLite CLI

Issue Overview: Unexpected File Access Permissions with –safe Flag in SQLite CLI

The SQLite command-line interface (CLI) includes a --safe flag designed to restrict access to functions and commands that could modify the host system or leak sensitive data. According to official documentation, enabling --safe should disable SQL functions such as readfile(), writefile(), edit(), fts3_tokenizer(), and load_extension(), along with blocking shell command execution via .shell or .system. However, users have observed that even with --safe enabled, readfile() and writefile() remain operational, allowing file system read/write operations. This behavior contradicts the documented security guarantees, creating confusion and potential security risks for users relying on --safe to sandbox untrusted scripts.

For example, running the following commands in SQLite 3.40.0 with --safe enabled demonstrates the issue:

sqlite3 --safe ":memory:" "SELECT writefile('secret.txt', randomblob(5))"  # Creates 'secret.txt'
sqlite3 --safe ":memory:" "SELECT readfile('secret.txt')"                  # Outputs file contents

The .shell command is correctly blocked in safe mode, but writefile() and readfile() bypass the restriction. This discrepancy suggests either a documentation error or a flaw in the CLI’s implementation of the --safe flag. The behavior persists across platforms, indicating it is not OS-specific.

Possible Causes: Incomplete Function Disabling and Security Model Ambiguity

1. CLI Implementation Oversight in Function Restriction
The SQLite CLI’s --safe mode operates by disabling specific functions and commands at runtime. However, the mechanism responsible for disabling readfile() and writefile() might have been incomplete or omitted in certain SQLite versions. For instance, prior to the fix in SQLite trunk (post-3.40.0), the sqlite3_set_authorizer function—used to enforce security policies—might not have been configured to block these file I/O functions when --safe is active. This would explain why .shell is blocked (its restriction is handled separately) while readfile()/writefile() remain accessible.

2. Documentation Ambiguity Regarding Security Scope
The documentation states that --safe disables "SQL functions that have potentially harmful side-effects," but it does not explicitly define "harmful." One interpretation is that --safe aims to prevent modification of the host environment (e.g., writing files, executing shell commands). Under this view, readfile() might be considered less critical since it does not modify data. However, the documentation explicitly lists readfile() as disabled, creating an expectation that it should be blocked regardless of its read/write nature. This mismatch between intent and specification contributes to confusion.

3. Security Context and Threat Model Assumptions
The --safe flag is designed for scenarios where users run untrusted CLI scripts. Its primary goal is to prevent scripts from altering the host system (e.g., deleting files, installing malware). However, readfile() could still expose sensitive data if an attacker crafts a script to read /etc/passwd or other confidential files. The original design might have underestimated the risk of read operations, focusing instead on write operations. This oversight becomes critical when users assume --safe provides comprehensive isolation.

4. Privilege Escalation and CVE-2022-46908 Implications
The ability to bypass --safe restrictions led to the assignment of CVE-2022-46908, classifying it as a security vulnerability. While SQLite’s developers argue that the risk is limited to users who run untrusted CLI scripts (a rare practice), the CVE highlights the potential for misuse. For example, a malicious script could use writefile() to overwrite configuration files or readfile() to exfiltrate credentials, depending on the user’s permissions. The vulnerability’s severity depends on the environment: a user with limited file system access poses less risk than one with administrative privileges.

Troubleshooting Steps, Solutions & Fixes: Mitigating the –safe Bypass

1. Verify SQLite Version and Functionality
First, confirm the SQLite CLI version and test whether readfile()/writefile() are restricted:

sqlite3 --version  # Check for versions ≥3.40.0 (fixed in trunk)
sqlite3 --safe ":memory:" "SELECT writefile('test.txt', 'demo'); SELECT readfile('test.txt');"  

If the commands succeed, the --safe restriction is not fully enforced.

2. Apply the Official Patch or Compile from Trunk
The SQLite development team addressed this issue in the trunk version (post-3.40.0). To apply the fix:

  • Download the latest source code from SQLite’s Fossil repository.
  • Compile the CLI with the patched authorization logic:
    ./configure && make sqlite3  
    
  • Replace the existing sqlite3 binary with the newly compiled version.

3. Use Workarounds for Unpatched Versions
If updating is not feasible, mitigate the risk by:

  • Revoking File System Permissions: Run the SQLite CLI with a restricted user account that has read/write access only to necessary directories.
  • Sandboxing: Use containerization (e.g., Docker) or OS-level sandboxing (e.g., Firejail) to isolate the CLI process.
  • Input Validation: Scrub untrusted scripts to remove readfile()/writefile() calls before execution.

4. Re-evaluate Threat Model and Documentation
Understand the intended scope of --safe:

  • It blocks modification primitives (.shell, writefile()) but historically allowed read-only functions.
  • The documentation’s inclusion of readfile() as a restricted function was incorrect prior to the patch. Adjust expectations accordingly.

5. Address CVE-2022-46908 in Security Policies
If your organization tracks CVEs:

  • Determine whether your workflow involves running untrusted CLI scripts with --safe. If not, the CVE’s applicability is limited.
  • For high-risk environments, enforce the use of patched SQLite versions or alternative isolation mechanisms.

6. Monitor for Official Releases
SQLite releases are infrequent and depend on thorough testing. Watch for version 3.41.0 or newer, which will include the fix. Until then, the trunk remains the authoritative source for the resolution.

7. Clarify Documentation and User Guidance
After applying fixes, review the updated documentation to ensure consistency. The --safe flag should now correctly block both readfile() and writefile(), aligning with the stated behavior. Educate users on the updated security guarantees and recommend against executing untrusted scripts without additional safeguards.

By systematically addressing implementation flaws, updating dependencies, and refining security practices, users can restore the intended protections of the --safe flag and prevent unintended file system access via SQLite CLI.

Related Guides

Leave a Reply

Your email address will not be published. Required fields are marked *