Skip to content

Commit c876a90

Browse files
committed
fix: resolve Linux FFI signature mismatch for main_l_get_tab_owner
1 parent aec150e commit c876a90

50 files changed

Lines changed: 634 additions & 416 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ body:
3939
id: plugin
4040
attributes:
4141
label: "Relevant Plugin (If Applicable)"
42-
description: "If this issue is related to a specific WebAssembly plugin (such as pTerm), specify its name and version."
43-
placeholder: "e.g., pTerm vx.x.x"
42+
description: "If this issue is related to a specific WebAssembly plugin, specify its name and version."
43+
placeholder: "e.g., plugin vx.x.x"
4444
validations:
45-
required: false
45+
required: false

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
* Shared WASM compilation module `tests/build_utils.py` to enforce `--allow-undefined` linker flags globally across all plugin builds.
1010
* Automated target binary copying and caching for Linux production builds.
1111
* Custom structured issue templates (Bug Report, Feature Request, Plugin Submission) and a Pull Request template in `.github/`.
12+
* Platform-agnostic OS detection FFI `host_get_platform` returning compile-time host platform evaluation (0 for Windows, 1 for Linux) to eliminate brittle environment variable parsing.
13+
* Linux shell executor backend (`sh.rs`) executing `/bin/sh -c` queries for the `pTerm` plugin.
14+
* Shell blacklisting security reinforcement: added `dash` shell to the banned executable list in the sandbox to block unauthorized access by untrusted plugins.
15+
* Shared `get_plugins_dir()` utility in `utils.rs` for consistent cross-platform plugin storage path resolution (`$HOME/.plug/plugins` on Linux, `%SystemDrive%\.plug\plugins` on Windows).
16+
* Permission gates for `get_env` and `net_post` WASM host imports — load-time import validation and runtime call-time enforcement against `plugin.toml` declarations.
1217

1318
### Fixed
1419
* Linux CI cross-compilation failure (`x86_64-pc-windows-gnu` target) — removed orphaned `.cargo/config.toml` that forced incorrect target resolution on Linux runners.
@@ -21,6 +26,14 @@
2126
* Linux production linker `undefined reference to 'g_headless_mode'` — guarded `extern "C"` declaration in `main_l.cpp` under `#ifdef PLUG_ENABLE_HEADLESS_MODE`; production builds now use `static constexpr bool g_headless_mode = false` with zero runtime cost.
2227
* GCC `-Wextern-initializer` warning in `main.cpp` — changed `extern "C" bool g_headless_mode = false` to block-form `extern "C" { bool g_headless_mode = false; }`.
2328
* Process hang on `/e` exit in headless mode — `g_cmd_thread` was blocking indefinitely on `g_cmd_cv.wait()`; `main_l_cleanup()` now sets `g_cmd_thread_running = false`, notifies the CV, and joins the thread before returning so the process exits cleanly within the test timeout window.
29+
* Headless CI broken pipe (`test_cli_lifecycle`) — moved `g_cmd_thread` and `g_headless_stdin_thread` startup from `on_tick()` frame-clock callback to `on_activate()` so stdin reader is live before any test data arrives; frame-clock callbacks are not guaranteed to fire before GApplication idle-hold expires under `xvfb-run`.
30+
* Linux `test_sandbox_rules` 120s timeout — plugin directory was hardcoded to `C:\.plug\plugins` (Windows-only); corrected to `$HOME/.plug/plugins` matching `proc.rs` `c_init()` runtime logic.
31+
* FFI Windows-centric symbol leakage — renamed `main_w_add_tab` to platform-neutral `host_add_tab` across `plugin_mgr.rs`, pTerm manifests, source plugin files, and test mocks (`ok_plugin.rs`, `rogue_plugin.rs`, `rogue_plugin_runtime.rs`) to prevent load-time link failures.
32+
* `pTerm` Linux shell execution compatibility — resolved execution errors on Linux hosts by adding `/sh` routing to the command parser, introducing the Linux shell backend, defaulting shell mode dynamically on Unix targets, and mapping E2E test assertions to platform-specific outputs.
33+
* Linux plugin install/delete path mismatch — `/plug` and `/plug-` handlers in `c_plug/mod.rs` and `c_plug/del/mod.rs` were hardcoded to `%SystemDrive%\.plug\plugins` (Windows-only); aligned with `c_init()` using shared `get_plugins_dir()` so Linux installs to `$HOME/.plug/plugins`.
34+
* Plugin dispatch race condition — `dispatch_plugin_cmd()` no longer removes the plugin instance from the in-memory registry during async execution; per-plugin execution locks serialize concurrent invocations instead.
35+
* README and startup banner exit command typo — corrected `/e-` references to `/e` in `main.cpp` and `main_l.cpp`.
36+
* FFI input sanitization — `c_parse()` routes command arguments through `to_c_string()` to strip embedded null bytes before CString conversion.
2437

2538
---
2639

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ cd plug.cross/linux && ./make.sh
5454
| `/?` | None | Displays help details. |
5555
| `/a` | None | Displays system environment and memory statistics. |
5656
| `/tab` | None | Spawns a new workspace tab. |
57+
| `/cmd` | `[query]` | Spawns a Windows Command Prompt tab or executes a CMD query. |
58+
| `/ps` | `[query]` | Spawns a Windows PowerShell tab or executes a PowerShell query. |
59+
| `/sh` | `[query]` | Spawns a Linux shell tab or executes a shell query. |
5760
| `/plug*` | None | Lists available online registry plugins and hashes. |
5861
| `/plug` | `[hash]` | Downloads and initializes a plugin. |
5962
| `/plug-` | `[hash]` | Unloads the specified plugin. |

docs/ARCHITECTURE.md

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,46 +18,14 @@ The framework splits operations into two layers connected via C FFI:
1818

1919
### C++ Functions (Called by Rust / Plugins)
2020
- `main_*_print_info(str)` / `main_*_print_error(str)`: Output logs to active text buffers.
21-
- `main_*_add_tab(name)`: Spawns a new tab context.
21+
- `main_*_add_tab(name)`: Spawns a new tab context. Note that the WASM FFI layer exposes a platform-neutral import named `host_add_tab` which the host runtime routes to these platform-specific functions.
2222
- `main_*_set_prompt_visibility(visible)`: Controls standard input prompt fields.
23-
- `main_*_get_tab_owner(idx)` / `main_*_set_tab_owner(idx, name)`: Syncs tab ownership state.
23+
- `main_*_get_tab_owner(idx, buf, max_len)` / `main_*_set_tab_owner(idx, name)`: Syncs tab ownership state. The `get` function writes the owner string into `buf` up to `max_len` to ensure buffer safety and avoid ABI crashes.
2424

2525
---
2626

2727
## Command Routing & Permissions
2828

2929
Inputs starting with `/` invoke built-in commands (e.g., `/tab` or `/plug`). Other inputs are dispatched to the WASM plugin owning the active tab.
3030

31-
If a plugin requests access to sensitive FFI imports (such as `host_exec` to run local shell subprocesses), the runtime validates its manifest permissions list (`plugin.toml`) before delegating the call to the host OS.
32-
33-
---
34-
35-
## Security Model & Sandbox Limits
36-
37-
### 1. The FFI Gate Architecture
38-
The WebAssembly sandbox isolates memory space and prevents direct OS API execution. System capabilities are exposed exclusively through imported FFI functions. The core runtime acts as a **binary gate**:
39-
- When a plugin attempts an operation (e.g., `host_exec`), the host runtime checks if the permission is listed in the plugin's manifest.
40-
- **Escape Vector by Design**: If a plugin is granted `host_exec` permission, the WASM boundary is crossed. A plugin with `host_exec` can potentially launch processes and interact with the host OS, making it a critical capability.
41-
42-
### 2. Execution Containment for Untrusted Plugins
43-
For third-party or untrusted plugins that require `host_exec` to run specific external tasks, the host runtime applies strict validation checks:
44-
- **Interpreter / LOLBin Ban**: Common shell binaries (`cmd.exe`, `powershell.exe`, `bash`, `python`) and code-execution LOLBins (`rundll32.exe`, `regsvr32.exe`) are explicitly banned.
45-
- **Canonical-Path Allowlist**: The target binary's canonical path must be explicitly mapped in the plugin manifest's `allowed_commands`.
46-
- **Regex Argument Filtering**: The command line arguments are evaluated against a linear-time regex pattern declared in the manifest.
47-
48-
### 3. Trust Tiers & Shell Emulator Bypass
49-
Core system utilities (like the shell terminal emulator `pTerm`) require execution of host shells to operate:
50-
- **Compile-Time Hash Pinning**: During compilation, the builder script computes the SHA-256 hash of the compiled `pTerm.wasm` binary and embeds it directly into the host Rust library (`plugin_mgr.rs` via `pterm_hash.txt`).
51-
- **Trust Bypass**: If a plugin's SHA-256 matches this hardcoded trust list (`TRUSTED_PLUGIN_HASHES`), it is marked as `is_trusted`. Trusted plugins bypass the interpreter bans and allowlist validations inside `host_exec`.
52-
- **Tamper Protection**: If a trusted plugin is modified, its hash changes. It falls back to the standard flow, requiring an `.integrity` file, and is blocked if tampered.
53-
54-
### 4. Cryptographic Integrity Validation
55-
- **Single-Read Verification**: To mitigate TOCTOU (Time-of-Check to Time-of-Use) filesystem attacks, plugin binaries are read into memory in a single syscall. The runtime computes the SHA-256 hash of this memory buffer and verifies it against the sidecar before Wasmer compiles it.
56-
- **Atomic Sidecar Writes**: Plugin installations write both the WASM binary and `.integrity` file to temporary files, followed by an atomic rename to prevent corrupted states.
57-
- **Registry Hash Pinning**: Remote plugin downloads are validated against SHA-256 hashes pinned in the official registry (`pluglists.json`) to prevent MITM attacks.
58-
- **Global Migration**: Pre-existing plugins are backfilled with `.integrity` files on startup, and a global marker is written. Post-migration, any loaded plugin lacking a valid `.integrity` sidecar is blocked.
59-
60-
### 5. Upstream Dependency Security Notes
61-
- **Wasmer & memmap2 Soundness (`RUSTSEC-2026-0186`)**:
62-
- The unsoundness vulnerability in `memmap2` (`RUSTSEC-2026-0186` for versions `< 0.9.11`) is currently ignored in [.cargo/audit.toml](file:///O:/prj/p01/.wip/plug/plug.app/rt/.cargo/audit.toml) because our current Wasmer version does not invoke the unsound functions (`advise_range` or `flush_range`).
63-
- **Upgrade Checklist**: Every time `wasmer` is upgraded, the developers must audit if the new Wasmer version invokes these `memmap2` range advising/flushing routines. If it does, `memmap2` must be upgraded to a safe patched version (e.g. `>= 0.9.11`) or Wasmer must be configured to bypass those calls to maintain full system safety.
31+
If a plugin requests access to sensitive FFI imports (such as `host_exec` to run local shell subprocesses, `get_env` to read environment variables, or `net_post` for outbound HTTP), the runtime validates its manifest permissions list (`plugin.toml`) at load time and again at call time before delegating to the host OS.

docs/BUILD.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ Run the platform-specific scripts to compile the Rust core runtime, build WASM p
2222
cd plug.cross\windows
2323
.\make.bat
2424
```
25-
Outputs: `release/x64/plug.exe` and `plugins/`
25+
Outputs: `plug.cross/release/x64/plug-x64.exe` and `plugins/`
2626

2727
### Linux
2828
```bash
2929
cd plug.cross/linux
3030
./make.sh
3131
```
32-
Outputs: `release_linux_x64/plug` and `plugins/`
32+
Outputs: `plug.cross/release/x86_64/plug-x64` and `plugins/`
3333

3434
---
3535

docs/PLUGIN_DEVELOPMENT.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ version = "1.0.0"
3939
author = "Dev"
4040
api_version = "xxx"
4141
permissions = [
42-
"host_exec", # Spawning local commands (CMD / PowerShell)
43-
"main_w_add_tab", # Spawning new tabs
44-
"host_set_tab_owner" # Taking tab ownership
42+
"host_exec", # Spawning local commands (CMD / PowerShell / shell)
43+
"host_add_tab", # Spawning new tabs
44+
"host_set_tab_owner", # Taking tab ownership
45+
"host_get_tab_label", # Reading active tab label
46+
"get_env", # Reading host environment variables
47+
"net_post" # HTTP POST requests from plugin runtime
4548
]
4649
```
4750

docs/TESTING_SYSTEM.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ tests/
2929

3030
To distinguish target environment mismatches from code failures, `preflight.py` executes before the orchestrator to verify requirements:
3131
- **Rust Target**: Verifies `wasm32-unknown-unknown` target is installed via `rustup target list --installed`. If missing, installs it or halts with setup instructions.
32-
- **Version Ranges**: Checks that `cargo` and `wasmer` match the verified toolchain versions (defined as hardcoded constants inside `preflight.py`) to prevent flaky behavior.
32+
- **Version Ranges**: Checks that `cargo` is present and reports the configured minimum cargo version from `preflight.py`.
3333
- **Zero-Dependency Path-Based Orphan Cleanup**:
3434
- To avoid installing third-party Python modules like `psutil` in CI/dev setups, path lookup uses native command utilities.
3535
- **Windows**: Invokes `powershell "Get-Process plug -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path"` to retrieve the executable path of matching active processes.
3636
- **Linux**: Reads `/proc/<pid>/exe` using `os.readlink()` for processes owned by the current user.
3737
- If a process matches the exact absolute target path of our compiled build binary in the workspace, it is terminated.
3838
- **OS Mapping**: Detects hosting environment parameters (`platform.system()`) to assign OS-specific configurations:
39-
- Binary names: `plug.exe` (Windows) vs `plug` (Linux).
40-
- Executable permissions: Performs `chmod +x` on Unix targets.
39+
- Binary names: `plug-{arch}.exe` (Windows) vs `plug-{arch}` (Linux).
40+
- Release directories: `plug.cross/release/{arch}` on Windows and `plug.cross/release/x86_64` for Linux x64.
4141
- Path separator configurations.
4242
- **Output**: Generates a unified `EnvContext` JSON object containing all pathing and platform context, which is forwarded to subsequent test scripts.
4343

@@ -65,7 +65,7 @@ Manifest configurations (`plugin.toml`) define the permission scope of the WASM
6565
```toml
6666
[[plugin]]
6767
name = "ok_plugin"
68-
permissions = ["main_w_add_tab", "host_exec"]
68+
permissions = ["host_add_tab", "host_exec"]
6969
```
7070
- **`rogue_plugin` Manifest (Load-time)**:
7171
```toml
@@ -76,7 +76,7 @@ Manifest configurations (`plugin.toml`) define the permission scope of the WASM
7676

7777
WASM files check:
7878
- **`ok_plugin.rs`**: Baseline plugin using permitted FFI calls. Verifies normal operations and prevents false-positive test results.
79-
- **`rogue_plugin.rs`** (Load-time check): A plugin containing host imports not declared in its `plugin.toml` manifest permissions. Verifies that the Wasmer runtime successfully rejects module initialization.
79+
- **`rogue_plugin.rs`** (Load-time check): A plugin containing host imports not declared in its `plugin.toml` manifest permissions (e.g. `host_add_tab`, `get_env`, `net_post`). Verifies that the Wasmer runtime successfully rejects module initialization.
8080
- **`rogue_plugin_runtime.rs`** (Runtime check): A plugin with valid import declarations that calls `host_exec` with arguments exceeding its permission scope (or attempts prohibited actions). Verifies that the runtime correctly catches and traps the thread during runtime execution.
8181

8282
### 3.3 Unit Tests (`tests/unit/`)
@@ -94,8 +94,8 @@ WASM files check:
9494
- `rogue_plugin_runtime sandbox trap check` (runtime execution containment).
9595
- `ok_plugin integrity mismatch check` (tampered WASM with stale `.integrity` sidecar is blocked).
9696
- `Global migration and post-migration bypass checks` (**Plan Cases A & B**: one-time global migration backfill validation and post-migration missing sidecar blocking).
97-
- `Trusted plugin bypass integrity file check` (**Plan Case C**: verifies `pTerm` with valid compiled hash loads successfully without sidecar and executes a real `dir` command via `host_exec`).
98-
- `Tampered trusted plugin block check` (**Plan Case D**: verifies tampered `pTerm` is blocked and fails-closed).
97+
- `Trusted plugin bypass integrity file check`
98+
- `Tampered trusted plugin block check`
9999

100100
### 3.5 End-to-End Tests (`tests/e2e/`)
101101
- **`test_cli_lifecycle.py`**: Spawns the production compiled `plug` executable under test scenarios using `subprocess.Popen` with `HIDE_CONSOLE=ON` (the standard production build configuration to ensure we test the identical binary shipped).

docs/plugins/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ Currently, the following official plugins are deployed and verified:
88

99
| Plugin Name | Version | Description | Documentation Link |
1010
|---|---|---|---|
11-
| **pTerm** | `1.0.0` | Default core terminal runner plugin bridging WASM container to host shells. | [Details](catalog/pTerm.md) |
11+
| **pTerm** | `1.0.1` | Default core terminal runner plugin bridging WASM container to host shells. | [Details](CATALOG/pTerm.md) |

docs/plugins/catalog/pTerm.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
## 1. Specifications
88

9-
- **Role**: Spawns and manages CMD and PowerShell sessions inside dedicated UI tabs.
10-
- **Required Permissions**: `host_exec`, `main_w_add_tab`, `host_set_tab_owner`, `host_get_tab_label`.
9+
- **Role**: Spawns and manages CMD, PowerShell (Windows), and sh (Linux) sessions inside dedicated UI tabs.
10+
- **Required Permissions**: `host_exec`, `host_add_tab`, `host_set_tab_owner`, `host_get_tab_label`.
1111
- **Trust Tier**: Recognized as a trusted core utility by matching the compile-time hardcoded SHA-256 hash list. Bypasses the sandbox interpreter bans (shells block) and canonical-path allowlist checks.
1212

1313
---
@@ -27,9 +27,9 @@ The diagram below illustrates the exact control flow when a user runs a shell co
2727
|
2828
v
2929
[WASM Sandbox (pTerm.wasm)]
30-
1. Calls `get_args` to retrieve input ("dir")
31-
2. Resolves active tab mode (PowerShell vs CMD)
32-
3. Prepend shell execution wrapper (e.g. `cmd /c dir` or `powershell -Command "dir"`)
30+
1. Calls `get_args` to retrieve input (e.g. "dir" or "ls")
31+
2. Resolves active tab mode (PowerShell, CMD, or SH)
32+
3. Prepend shell execution wrapper (e.g. `cmd /c dir`, `powershell -Command "dir"`, or `sh -c "ls"`)
3333
|
3434
v
3535
[Rust Host Runtime] -> 1. Verifies memory buffer SHA-256 matches compile-time TRUSTED_PLUGIN_HASHES
@@ -43,16 +43,18 @@ The diagram below illustrates the exact control flow when a user runs a shell co
4343

4444
## 3. Operations Routing
4545

46-
### Spawning a new Sub-Shell (`/cmd` or `/ps`)
47-
1. User types `/cmd` or `/ps`.
46+
### Spawning a new Sub-Shell (`/cmd`, `/ps`, or `/sh`)
47+
1. User types `/cmd`, `/ps`, or `/sh`.
4848
2. Rust router catches the command and invokes the `pTerm` module with initialization arguments.
49-
3. `pTerm` calls `main_w_add_tab("pTerm")` to request a new UI tab from the host.
50-
4. `pTerm` labels the tab owner name as `"CMD"` or `"PS"` using the `host_set_tab_owner` hook.
49+
3. `pTerm` calls `host_add_tab("pTerm")` to request a new UI tab from the host.
50+
4. `pTerm` labels the tab owner name as `"CMD"`, `"PS"`, or `"SH"` using the `host_set_tab_owner` hook.
5151
5. The tab context is initialized, and the user can now type terminal commands directly.
5252

5353
### Running Subprocess Commands
54-
1. User types `git status` in a tab labeled `"PS"`.
54+
1. User types `git status` in a tab labeled `"PS"` (or `ls -la` in a tab labeled `"SH"`).
5555
2. The input does not start with `/`, so Rust router resolves the active tab owner (`"pTerm"`) and delegates the call.
56-
3. `pTerm` checks the tab label, matches it to `"PS"`, and reformats the command:
57-
`powershell -NoProfile -Command "git status"`
56+
3. `pTerm` checks the tab label:
57+
- Matches `"PS"` -> reformats to: `powershell -NoProfile -Command "git status"`
58+
- Matches `"CMD"` -> reformats to: `cmd /c "git status"`
59+
- Matches `"SH"` -> reformats to: `sh -c "ls -la"`
5860
4. `pTerm` calls `host_exec` to execute the string on the host OS, streaming the outputs directly to the UI rendering buffers.

plug.app/cmn/inc/sys/api/linux/main_l.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#define MAIN_L_H
33

44
#ifdef __cplusplus
5-
// Forward declare for C++ code
5+
// forward declare for c++ code
66
typedef struct _GtkWidget GtkWidget;
77

88
#include <vector>
@@ -94,6 +94,14 @@ void main_l_cleanup(void);
9494
void main_l_print_banner(void);
9595
void main_l_print_info(const char* msg);
9696
void main_l_print_error(const char* msg);
97+
void main_l_set_prompt_visibility(int visible);
98+
void main_l_add_tab(const char* owner);
99+
void main_l_request_close(void);
100+
void main_l_replace_last_line(const char* msg);
101+
void main_l_set_tab_owner(int tab_idx, const char* owner);
102+
int main_l_get_tab_owner(int tab_idx, char* buf, int max_len);
103+
void main_l_set_tab_cwd(int tab_idx, const char* cwd);
104+
int main_l_get_current_print_tab(void);
97105

98106
#ifdef __cplusplus
99107
}

0 commit comments

Comments
 (0)