Files
git/unix-socket.c
Patrick Steinhardt c5fe29f696 unix-socket: fix memory leak when chdir(3p) fails
When trying to create a Unix socket in a path that exceeds the maximum
socket name length we try to first change the directory into the parent
folder before creating the socket to reduce the length of the name. When
this fails we error out of `unix_sockaddr_init()` with an error code,
which indicates to the caller that the context has not been initialized.
Consequently, they don't release that context.

This leads to a memory leak: when we have already populated the context
with the original directory that we need to chdir(3p) back into, but
then the chdir(3p) into the socket's parent directory fails, then we
won't release the original directory's path. The leak is exposed by
t0301, but only when running tests in a directory hierarchy whose path
is long enough to make the socket name length exceed the maximum socket
name length:

    Direct leak of 129 byte(s) in 1 object(s) allocated from:
        #0 0x5555555e85c6 in realloc.part.0 lsan_interceptors.cpp.o
        #1 0x55555590e3d6 in xrealloc ../wrapper.c:140:8
        #2 0x5555558c8fc6 in strbuf_grow ../strbuf.c:114:2
        #3 0x5555558cacab in strbuf_getcwd ../strbuf.c:605:3
        #4 0x555555923ff6 in unix_sockaddr_init ../unix-socket.c:65:7
        #5 0x555555923e42 in unix_stream_connect ../unix-socket.c:84:6
        #6 0x55555562a984 in send_request ../builtin/credential-cache.c:46:11
        #7 0x55555562a89e in do_cache ../builtin/credential-cache.c:108:6
        #8 0x55555562a655 in cmd_credential_cache ../builtin/credential-cache.c:178:3
        #9 0x555555700547 in run_builtin ../git.c:480:11
        #10 0x5555556ff0e0 in handle_builtin ../git.c:740:9
        #11 0x5555556ffee8 in run_argv ../git.c:807:4
        #12 0x5555556fee6b in cmd_main ../git.c:947:19
        #13 0x55555593f689 in main ../common-main.c:64:11
        #14 0x7ffff7a2a1fb in __libc_start_call_main (/nix/store/h7zcxabfxa7v5xdna45y2hplj31ncf8a-glibc-2.40-36/lib/libc.so.6+0x2a1fb) (BuildId: 0a855678aa0cb573cecbb2bcc73ab8239ec472d0)
        #15 0x7ffff7a2a2b8 in __libc_start_main@GLIBC_2.2.5 (/nix/store/h7zcxabfxa7v5xdna45y2hplj31ncf8a-glibc-2.40-36/lib/libc.so.6+0x2a2b8) (BuildId: 0a855678aa0cb573cecbb2bcc73ab8239ec472d0)
        #16 0x5555555ad1d4 in _start (git+0x591d4)

    DEDUP_TOKEN: ___interceptor_realloc.part.0--xrealloc--strbuf_grow--strbuf_getcwd--unix_sockaddr_init--unix_stream_connect--send_request--do_cache--cmd_credential_cache--run_builtin--handle_builtin--run_argv--cmd_main--main--__libc_start_call_main--__libc_start_main@GLIBC_2.2.5--_start
    SUMMARY: LeakSanitizer: 129 byte(s) leaked in 1 allocation(s).

Fix this leak.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-01-30 11:07:22 -08:00

140 lines
2.8 KiB
C

#include "git-compat-util.h"
#include "strbuf.h"
#include "unix-socket.h"
#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)
static int chdir_len(const char *orig, int len)
{
char *path = xmemdupz(orig, len);
int r = chdir(path);
free(path);
return r;
}
struct unix_sockaddr_context {
char *orig_dir;
};
static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)
{
if (!ctx->orig_dir)
return;
/*
* If we fail, we can't just return an error, since we have
* moved the cwd of the whole process, which could confuse calling
* code. We are better off to just die.
*/
if (chdir(ctx->orig_dir) < 0)
die("unable to restore original working directory");
free(ctx->orig_dir);
}
static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,
struct unix_sockaddr_context *ctx,
int disallow_chdir)
{
int size = strlen(path) + 1;
ctx->orig_dir = NULL;
if (size > sizeof(sa->sun_path)) {
const char *slash;
const char *dir;
struct strbuf cwd = STRBUF_INIT;
if (disallow_chdir) {
errno = ENAMETOOLONG;
return -1;
}
slash = find_last_dir_sep(path);
if (!slash) {
errno = ENAMETOOLONG;
return -1;
}
dir = path;
path = slash + 1;
size = strlen(path) + 1;
if (size > sizeof(sa->sun_path)) {
errno = ENAMETOOLONG;
return -1;
}
if (strbuf_getcwd(&cwd))
return -1;
ctx->orig_dir = strbuf_detach(&cwd, NULL);
if (chdir_len(dir, slash - dir) < 0) {
FREE_AND_NULL(ctx->orig_dir);
return -1;
}
}
memset(sa, 0, sizeof(*sa));
sa->sun_family = AF_UNIX;
memcpy(sa->sun_path, path, size);
return 0;
}
int unix_stream_connect(const char *path, int disallow_chdir)
{
int fd = -1, saved_errno;
struct sockaddr_un sa;
struct unix_sockaddr_context ctx;
if (unix_sockaddr_init(&sa, path, &ctx, disallow_chdir) < 0)
return -1;
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
goto fail;
if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
goto fail;
unix_sockaddr_cleanup(&ctx);
return fd;
fail:
saved_errno = errno;
if (fd != -1)
close(fd);
unix_sockaddr_cleanup(&ctx);
errno = saved_errno;
return -1;
}
int unix_stream_listen(const char *path,
const struct unix_stream_listen_opts *opts)
{
int fd = -1, saved_errno;
int backlog;
struct sockaddr_un sa;
struct unix_sockaddr_context ctx;
unlink(path);
if (unix_sockaddr_init(&sa, path, &ctx, opts->disallow_chdir) < 0)
return -1;
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
goto fail;
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
goto fail;
backlog = opts->listen_backlog_size;
if (backlog <= 0)
backlog = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG;
if (listen(fd, backlog) < 0)
goto fail;
unix_sockaddr_cleanup(&ctx);
return fd;
fail:
saved_errno = errno;
if (fd != -1)
close(fd);
unix_sockaddr_cleanup(&ctx);
errno = saved_errno;
return -1;
}