Skip to content

Security and Architectural Audit: Unified tcp+acw implementation in tor_control_panel #2

Description

@sans1924-prog

Author's Note: This report and all findings herein were researched and written entirely by me, the author. LLMs or AI assistants were used in the drafting or editing of this content only for formatting the Markdown or polishing English. Please excuse any potential formatting inconsistencies, as this document was prepared manually, although many parts were further rewritten by myself.

Target Subsystems

  • usr/lib/python3/dist-packages/tor_control_panel/tor_control_panel.py
  • usr/lib/python3/dist-packages/tor_control_panel/tor_bootstrap.py
  • usr/lib/python3/dist-packages/tor_control_panel/torrc_gen.py
  • usr/lib/python3/dist-packages/tor_control_panel/tor_status.py

Executive Summary

A technical security and architectural stability audit of the recently unified tcp+acw implementation reveals multiple critical vulnerabilities. These range from unhandled thread lifecycle executions that trigger interface deadlocks and silent failures, to fragile file parsing patterns that cause local configuration corruption and credential exposures.


📊 Vulnerability Triage Matrix

Component Target File Severity Primary Risk Vector
Threading Engine tor_bootstrap.py / tor_control_panel.py High Resource leaks via uncooperative .terminate() blocks and 10s synchronous kernel sleeps.
State Parser tor_status.py High Fail-open risks via naive substring matching and thread crashes via unhandled file reads.
Config Generator torrc_gen.py Medium Global privilege mutation via inline DNS overrides and dynamic state tracking breakage via a string typo.
Credential Storage torrc_gen.py Medium Local plaintext credential exposure due to loose temporary file permissions.

🔍 Deep-Dive Findings

1. Resource Leakage and Anonymity Traps via Uncooperative QThread.terminate()

  • Location: tor_control_panel.py (Inside update_bootstrap(), restart_tor(), stop_tor(), and quit())
  • The Bug: When intercepting connection errors, restarts, or early application exits, the main interface loop repeatedly invokes hard kills on the background thread instance:
self.bootstrap_thread.terminate()

Impact: In PyQt, calling .terminate() sends an immediate operating system interrupt that halts the worker execution context instantly. It prevents Python from unwinding the stack, executing finally blocks, or running socket cleanup routines. The control connection socket remains open in the background, keeping the routing layer active even if the interface states claim it has been successfully torn down.

2. Synchronous Delays Induce 10-Second Graphical UI Deadlocks

Location: tor_bootstrap.py (Inside connect_to_control_port())

The Bug: Upon capturing socket initialization blocks or configuration discrepancies, the background thread executes an unbuffered, synchronous kernel wait:

time.sleep(10)

Impact: Synchronous sleeps block the worker event execution frame entirely. If a user cancels the panel window or changes state tabs while this timer is active, the main UI loop executes self.bootstrap_thread.wait(). The primary application interface is forced to hang until the worker's 10-second sleep constraint completely expires.

3. Silent Exception Catching Triggers Permanent Interface Hangs

Location: tor_bootstrap.py (Lines 125–137 inside connect_to_control_port())

The Bug: Multiple core authentication exception loops return empty payloads without passing diagnostic events back to the primary framework:

except stem.connection.IncorrectCookieSize:
    return None
except stem.connection.CookieAuthRejected:
    return None
except:
    return None

Impact: If authentication fails due to incorrect sizes, permission issues, or generic environment blocks, the background execution frame exits silently. Because no status signal is emitted to the controller window, the interface remains indefinitely frozen on the "Authenticating the Tor Controller..." progress loader without displaying an error message.

4. Fragile State Classification and Thread Crashes in tor_status

Location: tor_status.py (Inside tor_enabled_check(), set_enabled(), and set_disabled())

The Bug: The classification utility handles state detection using broad substring validation boundaries (if 'DisableNetwork' in line:) while the mandatory file safety checks remain commented out:

# if os.path.exists(torrc_file_path):
with open(torrc_file_path, 'r') as f:

Impact: Substring tracking inadvertently captures disabled syntax inside commented documentation (e.g., # DisableNetwork 1), prompting set_enabled() to execute structural text updates via generic .replace() logic on comments. Furthermore, if the configuration drop-in file is missing, unreadable, or locked during execution, the lack of an exception trap raises an unhandled FileNotFoundError, crashing the panel's active runner instantly.

5. Config State Breakage via Target String Spelling Typo

Location: torrc_gen.py (Inside gen_torrc()) and tor_status.py (Inside parse_torrc())

The Bug: The generation script writes a tracking string using one format, but the parser validates the configuration file using an inconsistent, misspelled variant:

# torrc_gen.py writes:
torrc_content.append('# Custom bridges are used\n')
# tor_status.py

reads:
use_custom_bridges = '# Custom briges are used' in torrc_file_contents
Impact: Because the parsing conditional checks for briges (missing the d), use_custom_bridges evaluates to False under all standard operations. The system is structurally unable to remember if custom bridges were specified, causing the application to destroy custom configurations with standard profile assets on the next UI execution pass.

6. System State Mutation Violated via Inline DNS Workarounds

Location: torrc_gen.py (Inside gen_torrc())

The Bug: Triggering configuration generation routines for specific transport mechanisms (meek or snowflake) invokes live system settings changes:

if bridge_type.startswith('meek') or bridge_type.startswith('snowflake'):
    edit_etc_resolv_conf_add()

Impact: This violates the Single Responsibility Principle (SRP). A text generator module should behave as a pure function that emits string data. Forcing a configuration parser to mutate global network infrastructure properties like /etc/resolv.conf introduces unnecessary privilege dependencies and prevents the safe evaluation of isolated dry-run generations.

7. Plaintext Proxy Credential Exposure Risks

Location: torrc_gen.py

The Bug: When writing authenticated SOCKS5 proxy options to /usr/local/etc/torrc.d/40_tor_control_panel.conf, plaintext credentials are saved directly to the disk without initializing explicit filesystem access masks or running discrete chmod 0600 permission limits on creation.

Impact: On multi-user workstation deployments, any unprivileged local actor can inspect the world-readable configuration target path and extract active upstream proxy credentials directly.

🛠️ Proposed Architectural Remediation
Adopt Cooperative Thread Transitions: Deprecate the use of .terminate(). Introduce a thread-safe atomic cancellation flag (e.g., self._is_cancelled) within execution tracks to let the worker thread cleanly close its open control connections before halting.

Implement Non-Blocking Event Timers: Replace all instances of time.sleep(10) inside long-running loops with non-blocking, asynchronous timeouts using QTimer.singleShot() to maintain an active GUI event frame during failure loops.

Enforce Complete Signal Trapping: Ensure that all exception branches within the connection and authentication loops safely pass descriptive event signals to the listener window before returning None.

Enforce Regex and Strict String Typing: Replace naive substring matching loops with robust line-anchored regex validations (^DisableNetwork\s+[01]). Correct the spelling discrepancy (bridges vs briges) within parse_torrc() to stabilize custom bridge preservation.

Enforce Hardened File System Defenses: Restore proper file checking boundaries before performing file reads, wrap data writing utilities in explicit exception blocks, and enforce tight file access parameters (chmod 0600) before populating local configuration storage trees with active proxy parameters.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions