Firstly, thank you for this. I've been using it for a bit now and it's excellent.
I went for a more in depth review of the code this week and found some security problems in the plugin loader.
Specifically:
1. IPC has no auth
src/library/plugin/plugin.c:948-973
Any process running as your user can connect to $XDG_RUNTIME_DIR/bolt-launcher/ipc-0 and tell the plugin loader to run arbitrary Lua:
plugin->path = malloc(header->path_size); // from network
_bolt_ipc_receive(fd, plugin->path, header.path_size);
There's no validation on:
- Who's sending the message
- What path they're sending
- Size values (can overflow at line 963:
path_size + main_size + 1)
Attack: malicious browser extension → connects to socket → loads /tmp/evil.lua into game process.
Fix: SO_PEERCRED auth, validate paths with realpath(), check sizes.
2. Path traversal escapes Lua sandbox
src/library/plugin/plugin_api.c:347-380
Good news: you sandbox Lua - no io, os, or debug libraries. Excellent!
Bad news: dofile() and loadfile() can escape. Your path validation doesn't check for path traversal using ..:
for (size_t i = 0; i < config_length; i += 1) {
if (config[i] == '\\') config[i] = '/';
}
So, we can do something like:
dofile("../../.ssh/id_rsa") -- uh oh
Fix:
if (strstr(config, "..") != NULL) return luaL_error(state, "no");
// or use realpath() and verify it's under plugin->path
Other issues
While not as critical, I also found a few other issues worth looking at:
- IPC size limits (
plugin.c:856-882, 948-973) - No max size enforced on message_size, could DoS by allocating huge amounts. Consider #define MAX_MESSAGE_SIZE (10*1024*1024).
- Screen capture permissions (
plugin_posix.c:31) - Shared memory is 0644, any process can read. Change to 0600.
- Potential Race on
grabbed_window_id (plugin.c:1104-1147) - Input and render threads touch it without a lock.
- Plugin signatures - No hash verification on loaded plugins. It would be good to ensure the validity of what we're loading.
- Buffer bounds check (
plugin_api.c:140-172) - offset + sizeof(TYPE) can overflow, should be offset > data_size - sizeof(TYPE).
- Socket permissions (client_posix.cxx:28) - Relies on XDG_RUNTIME_DIR protection, no explicit chmod.
I'd be happy to take a crack at addressing some of these problems if you agree.
Firstly, thank you for this. I've been using it for a bit now and it's excellent.
I went for a more in depth review of the code this week and found some security problems in the plugin loader.
Specifically:
### 1. IPC has no auth
`src/library/plugin/plugin.c:948-973`
Any process running as your user can connect to `$XDG_RUNTIME_DIR/bolt-launcher/ipc-0` and tell the plugin loader to run arbitrary Lua:
```c
plugin->path = malloc(header->path_size); // from network
_bolt_ipc_receive(fd, plugin->path, header.path_size);
```
There's no validation on:
- Who's sending the message
- What path they're sending
- Size values (can overflow at line 963: `path_size + main_size + 1`)
Attack: malicious browser extension → connects to socket → loads `/tmp/evil.lua` into game process.
**Fix:** `SO_PEERCRED` auth, validate paths with `realpath()`, check sizes.
### 2. Path traversal escapes Lua sandbox
`src/library/plugin/plugin_api.c:347-380`
Good news: you sandbox Lua - no `io`, `os`, or `debug` libraries. Excellent!
Bad news: `dofile()` and `loadfile()` can escape. Your path validation doesn't check for path traversal using `..`:
```c
for (size_t i = 0; i < config_length; i += 1) {
if (config[i] == '\\') config[i] = '/';
}
```
So, we can do something like:
```lua
dofile("../../.ssh/id_rsa") -- uh oh
```
**Fix:**
```c
if (strstr(config, "..") != NULL) return luaL_error(state, "no");
// or use realpath() and verify it's under plugin->path
```
### Other issues
While not as critical, I also found a few other issues worth looking at:
- IPC size limits (`plugin.c:856-882`, `948-973`) - No max size enforced on message_size, could DoS by allocating huge amounts. Consider `#define MAX_MESSAGE_SIZE (10*1024*1024)`.
- Screen capture permissions (`plugin_posix.c:31`) - Shared memory is 0644, any process can read. Change to 0600.
- Potential Race on `grabbed_window_id` (`plugin.c:1104-1147`) - Input and render threads touch it without a lock.
- Plugin signatures - No hash verification on loaded plugins. It would be good to ensure the validity of what we're loading.
- Buffer bounds check (`plugin_api.c:140-172`) - `offset + sizeof(TYPE)` can overflow, should be `offset > data_size - sizeof(TYPE)`.
- Socket permissions (client_posix.cxx:28) - Relies on XDG_RUNTIME_DIR protection, no explicit chmod.
I'd be happy to take a crack at addressing some of these problems if you agree.