Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(RHEL-18302) Avoid memory allocation in freeze() #419

Merged
merged 4 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 59 additions & 26 deletions src/basic/fd-util.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
#include "stdio-util.h"
#include "util.h"

/* The maximum number of iterations in the loop to close descriptors in the fallback case
* when /proc/self/fd/ is inaccessible. */
#define MAX_FD_LOOP_LIMIT (1024*1024)

int close_nointr(int fd) {
assert(fd >= 0);

Expand Down Expand Up @@ -188,44 +192,73 @@ _pure_ static bool fd_in_set(int fd, const int fdset[], size_t n_fdset) {
return false;
}

int close_all_fds(const int except[], size_t n_except) {
_cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
int r = 0;
static int get_max_fd(void) {
struct rlimit rl;
rlim_t m;

assert(n_except == 0 || except);
/* Return the highest possible fd, based RLIMIT_NOFILE, but enforcing FD_SETSIZE-1 as lower boundary
* and INT_MAX as upper boundary. */

d = opendir("/proc/self/fd");
if (!d) {
struct rlimit rl;
int fd, max_fd;
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
return -errno;

/* When /proc isn't available (for example in chroots) the fallback is brute forcing through the fd
* table */
m = MAX(rl.rlim_cur, rl.rlim_max);
if (m < FD_SETSIZE) /* Let's always cover at least 1024 fds */
return FD_SETSIZE-1;

assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0);
if (m == RLIM_INFINITY || m > INT_MAX) /* Saturate on overflow. After all fds are "int", hence can
* never be above INT_MAX */
return INT_MAX;

if (rl.rlim_max == 0)
return -EINVAL;
return (int) (m - 1);
}

/* Let's take special care if the resource limit is set to unlimited, or actually larger than the range
* of 'int'. Let's avoid implicit overflows. */
max_fd = (rl.rlim_max == RLIM_INFINITY || rl.rlim_max > INT_MAX) ? INT_MAX : (int) (rl.rlim_max - 1);
int close_all_fds_without_malloc(const int except[], size_t n_except) {
int max_fd, r = 0;

for (fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) {
int q;
assert(n_except == 0 || except);

if (fd_in_set(fd, except, n_except))
continue;
/* This is the inner fallback core of close_all_fds(). This never calls malloc() or opendir() or so
* and hence is safe to be called in signal handler context. Most users should call close_all_fds(),
* but when we assume we are called from signal handler context, then use this simpler call
* instead. */

q = close_nointr(fd);
if (q < 0 && q != -EBADF && r >= 0)
r = q;
}
max_fd = get_max_fd();
if (max_fd < 0)
return max_fd;

return r;
/* Refuse to do the loop over more too many elements. It's better to fail immediately than to
* spin the CPU for a long time. */
if (max_fd > MAX_FD_LOOP_LIMIT)
return log_debug_errno(EPERM,
"Refusing to loop over %d potential fds.",
max_fd);

for (int fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) {
int q;

if (fd_in_set(fd, except, n_except))
continue;

q = close_nointr(fd);
if (q < 0 && q != -EBADF && r >= 0)
r = q;
}

return r;
}

int close_all_fds(const int except[], size_t n_except) {
_cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
int r = 0;

assert(n_except == 0 || except);

d = opendir("/proc/self/fd");
if (!d)
return close_all_fds_without_malloc(except, n_except); /* ultimate fallback if /proc/ is not available */

FOREACH_DIRENT(de, d, return -errno) {
int fd = -1, q;

Expand Down
1 change: 1 addition & 0 deletions src/basic/fd-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ int fd_nonblock(int fd, bool nonblock);
int fd_cloexec(int fd, bool cloexec);

int close_all_fds(const int except[], size_t n_except);
int close_all_fds_without_malloc(const int except[], size_t n_except);

int same_fd(int a, int b);

Expand Down
6 changes: 4 additions & 2 deletions src/basic/process-util.c
Original file line number Diff line number Diff line change
Expand Up @@ -991,8 +991,10 @@ _noreturn_ void freeze(void) {

log_close();

/* Make sure nobody waits for us on a socket anymore */
close_all_fds(NULL, 0);
/* Make sure nobody waits for us (i.e. on one of our sockets) anymore. Note that we use
* close_all_fds_without_malloc() instead of plain close_all_fds() here, since we want this function
* to be compatible with being called from signal handlers. */
(void) close_all_fds_without_malloc(NULL, 0);

sync();

Expand Down