fix(utils): use parent directory fd for bind on long Unix socket paths#2050
fix(utils): use parent directory fd for bind on long Unix socket paths#2050giuseppe merged 1 commit intocontainers:mainfrom
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request resolves a critical issue where Unix domain sockets failed to bind or bound incorrectly when their paths exceeded the Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a clever fix for handling long Unix domain socket paths by using a file descriptor to the parent directory. The overall approach is sound. However, the new implementation for parsing the path introduces a bug when handling paths with trailing slashes, which could lead to incorrect behavior. I have provided a suggestion to fix this correctness issue. Additionally, I've included a recommendation to refactor the path construction logic for improved safety and readability.
src/libcrun/utils.c
Outdated
| cleanup_free char *path_copy = xstrdup (path); | ||
| const char *parent_dir = dirname (path_copy); | ||
| const char *base = strrchr (path, '/'); | ||
| base = base ? base + 1 : path; |
There was a problem hiding this comment.
The logic to extract the basename using strrchr on the original path is flawed when the path has trailing slashes. For a path like /tmp/socket/, strrchr finds the trailing /, and base becomes an empty string, which is not the intended socket name.
To correctly handle all path variations, it's safer to use basename() from <libgen.h>, which is designed to handle these edge cases. Since both dirname() and basename() can modify their string arguments, you should operate on separate copies of the path to ensure correctness.
cleanup_free char *dpath = xstrdup (path);
cleanup_free char *bpath = xstrdup (path);
const char *parent_dir = dirname (dpath);
const char *base = basename (bpath);
src/libcrun/utils.c
Outdated
| size_t proc_len = strlen (name_buf); | ||
| size_t base_len = strlen (base); | ||
| if (proc_len + 1 + base_len >= sizeof (addr.sun_path)) | ||
| return crun_make_error (err, ENAMETOOLONG, "socket path too long: `%s`", path); | ||
|
|
||
| memcpy (addr.sun_path, name_buf, proc_len); | ||
| addr.sun_path[proc_len] = '/'; | ||
| memcpy (addr.sun_path + proc_len + 1, base, base_len + 1); |
There was a problem hiding this comment.
The manual construction of the socket path using strlen and memcpy can be simplified and made safer by using snprintf. This function handles buffer boundaries and null termination automatically, reducing the risk of off-by-one errors and improving readability.
int n = snprintf (addr.sun_path, sizeof (addr.sun_path), "%s/%s", name_buf, base);
if (n < 0 || (size_t) n >= sizeof (addr.sun_path))
return crun_make_error (err, ENAMETOOLONG, "socket path too long: `%s`", path);There was a problem hiding this comment.
@jnovy what do you think about this suggestion though?
do we have any issue or reproducer for this? I've never seen it before |
|
@giuseppe There's no filed issue - I found this while running the test suite from a deep working directory. The reproducer is straightforward: When crun is built and tested from a long path like which is ~117 bytes - exceeding the 108-byte The existing long-path fallback in Note that To reproduce: mkdir -p /tmp/deep/nested/path/that/is/long/enough/to/exceed/the/limit/for/sun_path
cd /tmp/deep/nested/path/that/is/long/enough/to/exceed/the/limit/for/sun_path
git clone https://github.com/containers/crun.git
cd crun
./autogen.sh && ./configure && make
cd tests
./tests_libcrun_utilsThe |
When the Unix domain socket path exceeds sun_path limit (108 bytes), open_unix_domain_socket() was incorrectly using /proc/self/fd/N of the socket fd itself as the bind address. This caused "Address already in use" errors because /proc/self/fd/N for a socket does not resolve to a filesystem path suitable for bind(). Fix by opening the parent directory with O_PATH and constructing the bind address as /proc/self/fd/N/basename, matching the approach already used in open_unix_domain_client_socket() for connect(). This fixes the sd-notify, sd-notify-file, and sd-notify-env test failures that occurred when the test working directory path was long enough to exceed the sun_path limit. Signed-off-by: Jindrich Novy <jnovy@redhat.com>
a70f55a to
cf27a14
Compare
Problem
open_unix_domain_socket()fails withEADDRINUSE(or silently binds to the wrong path) when the socket path exceeds the 108-bytesun_pathlimit ofstruct sockaddr_un.The current code attempts to work around long paths by using
/proc/self/fd/NwhereNis the socket file descriptor itself:This is incorrect because
/proc/self/fd/<socket_fd>points tosocket:[inode], not to a filesystem path. The kernel cannot resolve this to a bindable address, sobind()fails. This affects the sd-notify socket path which is constructed as<state_dir>/notify/notifyand can easily exceed 108 bytes with long container IDs or deepXDG_RUNTIME_DIRpaths.Fix
Instead of using the socket's own fd, open the parent directory of the target path with
O_PATH | O_DIRECTORY, then construct the bind address as/proc/self/fd/<dirfd>/<basename>. The kernel resolves/proc/self/fd/<dirfd>to the actual directory path via procfs, keeping the finalsun_pathwell within the 108-byte limit (typically ~30 bytes for the/proc/self/fd/N/prefix plus the basename).The fix:
dirname()/strrchr()open(parent, O_PATH | O_DIRECTORY | O_CLOEXEC)sun_pathas/proc/self/fd/<dirfd>/<basename>sun_pathbefore callingbind()Short paths (< 108 bytes) are unaffected and follow the original direct copy path.