Skip to content

Improve fm-shim-backend security and D-Bus error handling#358

Open
assisted-by-ai wants to merge 1 commit intoKicksecure:masterfrom
assisted-by-ai:claude/security-review-fm-shim-8hki2
Open

Improve fm-shim-backend security and D-Bus error handling#358
assisted-by-ai wants to merge 1 commit intoKicksecure:masterfrom
assisted-by-ai:claude/security-review-fm-shim-8hki2

Conversation

@assisted-by-ai
Copy link
Copy Markdown

Summary

This PR enhances the security and robustness of the fm-shim-backend service by implementing file descriptor cleanup in child processes, improving D-Bus error handling, and adding systemd hardening options.

Key Changes

File Descriptor Management:

  • Added automatic cleanup of inherited file descriptors (except stdin/stdout/stderr) in child processes by scanning /proc/self/fd before executing the frontend process
  • Prevents accidental leakage of sensitive file descriptors to child processes

D-Bus Improvements:

  • Changed D-Bus name request to use DBUS_NAME_FLAG_DO_NOT_QUEUE flag, preventing the service from queuing if the name is already taken
  • Converted queued name request from a warning to a fatal error, treating it as a security issue
  • Added proper D-Bus error replies for invalid object paths, interfaces, and methods instead of silently ignoring them
  • Added cleanup of D-Bus connection and error data structures on exit

Systemd Hardening:

  • Added security-focused systemd service options:
    • NoNewPrivileges=yes - Prevents privilege escalation
    • ProtectSystem=strict - Read-only access to system directories
    • ProtectHome=read-only - Read-only access to home directory
    • PrivateTmp=yes - Private temporary directory
    • RestrictNamespaces=yes - Restrict namespace creation
    • RestrictRealtime=yes - Disable real-time scheduling
    • MemoryDenyWriteExecute=yes - Prevent executable memory allocation
    • LockPersonality=yes - Lock process personality

Implementation Details

  • Uses opendir() and readdir() to enumerate file descriptors safely
  • Validates file descriptor numbers using strtol() with proper error checking
  • Maintains compatibility by preserving the directory file descriptor during enumeration
  • D-Bus error replies are only sent when the client expects a reply

https://claude.ai/code/session_01CgrrFhhVWtqtfhvJdEcBWm

…ene, systemd sandboxing, and cleanup

- Add DBUS_NAME_FLAG_DO_NOT_QUEUE and terminate on IN_QUEUE to prevent
  silent name takeover after another owner exits
- Send proper D-Bus error replies for unrecognized methods, wrong object
  paths, and wrong interfaces instead of leaving callers hanging
- Close inherited file descriptors in child process before execve to
  avoid leaking the D-Bus socket to the frontend
- Add systemd sandboxing directives (NoNewPrivileges, ProtectSystem,
  ProtectHome, PrivateTmp, etc.) to the user service
- Add explicit dbus_connection_unref/dbus_error_free on main loop exit

https://claude.ai/code/session_01CgrrFhhVWtqtfhvJdEcBWm
Copy link
Copy Markdown
Contributor

@ArrayBolt3 ArrayBolt3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Integrated the useful parts of this into ArrayBolt3@af4f229. Notes below.

Comment on lines -335 to +352
DBUS_NAME_FLAG_REPLACE_EXISTING,
DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, no, this is not what we want to do.

Comment on lines -349 to +366
warnx("Requested 'org.freedesktop.FileManager1' name from D-Bus, but was placed in queue.");
warnx("This may be a security risk! Please report this bug!");
/*
* sd_notify(0, "READY=1") is intentionally omitted here, so that
* systemcheck will show a warning about the system not being ready
*/
sd_notify(0, "STATUS=Running, security issue detected");
errx(1, "Requested 'org.freedesktop.FileManager1' name from D-Bus, but was placed in queue. This is a security risk! Please report this bug!");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is not what we want to do. This makes the security risk worse.

Comment on lines +13 to +20
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=yes
RestrictNamespaces=yes
RestrictRealtime=yes
MemoryDenyWriteExecute=yes
LockPersonality=yes
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe all of these restrictions will be inherited by the child process (the file manager) and any processes it launches, which probably will lead to strange behavior. Even MemoryDenyWriteExecute=yes might be problematic if the user launches a terminal from within the file manager, then launches a JIT-compiled applications from there.

Comment on lines +407 to +414
if (dbus_message_get_no_reply(dbus_msg) == FALSE) {
DBusMessage *err_reply = dbus_message_new_error(dbus_msg,
DBUS_ERROR_UNKNOWN_OBJECT, "Unknown object path");
if (err_reply != NULL) {
dbus_connection_send(dbus_conn, err_reply, NULL);
dbus_message_unref(err_reply);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted with some refactoring to avoid duplication.

Comment on lines +426 to +433
if (dbus_message_get_no_reply(dbus_msg) == FALSE) {
DBusMessage *err_reply = dbus_message_new_error(dbus_msg,
DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface");
if (err_reply != NULL) {
dbus_connection_send(dbus_conn, err_reply, NULL);
dbus_message_unref(err_reply);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted with refactoring.

Comment on lines +452 to +459
if (dbus_message_get_no_reply(dbus_msg) == FALSE) {
DBusMessage *err_reply = dbus_message_new_error(dbus_msg,
DBUS_ERROR_UNKNOWN_METHOD, "Unknown method");
if (err_reply != NULL) {
dbus_connection_send(dbus_conn, err_reply, NULL);
dbus_message_unref(err_reply);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted with refactoring.

Comment on lines +471 to +473

dbus_connection_unref(dbus_conn);
dbus_error_free(&error_data);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted.

Comment on lines -195 to +212
/* child */
/* child - close inherited fds (except stdin/stdout/stderr) */
DIR *fd_dir = opendir("/proc/self/fd");
if (fd_dir != NULL) {
int dir_fd = dirfd(fd_dir);
struct dirent *fd_entry;
while ((fd_entry = readdir(fd_dir)) != NULL) {
char *endptr = NULL;
long fd_num = strtol(fd_entry->d_name, &endptr, 10);
if (endptr == fd_entry->d_name || *endptr != '\0') {
continue;
}
if (fd_num >= 3 && fd_num != (long)dir_fd) {
close((int)fd_num);
}
}
closedir(fd_dir);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is safe since even if libdbus uses multiple threads in the background (which hopefully it doesn't but you never know!), fork() only gives us a single thread. libdbus should set CLOEXEC on its socket file descriptor from what I can tell, and we don't open anything else, so this shouldn't be strictly necessary, but I guess it's a useful hardening measure, so I'll accept the idea. Implementation-wise though, this should be using the close_range() system call, not manual iteration through things in /proc.

#include <unistd.h>
#include <assert.h>
#include <sys/wait.h>
#include <dirent.h>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary since I'm using close_range() instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants