Improve fm-shim-backend security and D-Bus error handling#358
Improve fm-shim-backend security and D-Bus error handling#358assisted-by-ai wants to merge 1 commit intoKicksecure:masterfrom
Conversation
…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
ArrayBolt3
left a comment
There was a problem hiding this comment.
Integrated the useful parts of this into ArrayBolt3@af4f229. Notes below.
| DBUS_NAME_FLAG_REPLACE_EXISTING, | ||
| DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE, |
There was a problem hiding this comment.
Again, no, this is not what we want to do.
| 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!"); |
There was a problem hiding this comment.
No, this is not what we want to do. This makes the security risk worse.
| NoNewPrivileges=yes | ||
| ProtectSystem=strict | ||
| ProtectHome=read-only | ||
| PrivateTmp=yes | ||
| RestrictNamespaces=yes | ||
| RestrictRealtime=yes | ||
| MemoryDenyWriteExecute=yes | ||
| LockPersonality=yes |
There was a problem hiding this comment.
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.
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
Accepted with some refactoring to avoid duplication.
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
Accepted with refactoring.
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
Accepted with refactoring.
|
|
||
| dbus_connection_unref(dbus_conn); | ||
| dbus_error_free(&error_data); |
| /* 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); | ||
| } |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
Unnecessary since I'm using close_range() instead.
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:
/proc/self/fdbefore executing the frontend processD-Bus Improvements:
DBUS_NAME_FLAG_DO_NOT_QUEUEflag, preventing the service from queuing if the name is already takenSystemd Hardening:
NoNewPrivileges=yes- Prevents privilege escalationProtectSystem=strict- Read-only access to system directoriesProtectHome=read-only- Read-only access to home directoryPrivateTmp=yes- Private temporary directoryRestrictNamespaces=yes- Restrict namespace creationRestrictRealtime=yes- Disable real-time schedulingMemoryDenyWriteExecute=yes- Prevent executable memory allocationLockPersonality=yes- Lock process personalityImplementation Details
opendir()andreaddir()to enumerate file descriptors safelystrtol()with proper error checkinghttps://claude.ai/code/session_01CgrrFhhVWtqtfhvJdEcBWm