Skip to content
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ LIBEXECDIR ?= ${PREFIX}/libexec
PKG_CONFIG ?= pkg-config
HEADERS := $(wildcard src/*.h)

OBJS := src/conmon.o src/cmsg.o src/ctr_logging.o src/utils.o src/cli.o src/globals.o src/cgroup.o src/conn_sock.o src/oom.o src/ctrl.o src/ctr_stdio.o src/parent_pipe_fd.o src/ctr_exit.o src/runtime_args.o src/close_fds.o src/seccomp_notify.o
OBJS := src/conmon.o src/cmsg.o src/ctr_logging.o src/utils.o src/cli.o src/globals.o src/cgroup.o src/conn_sock.o src/oom.o src/ctrl.o src/ctr_stdio.o src/parent_pipe_fd.o src/ctr_exit.o src/runtime_args.o src/close_fds.o src/seccomp_notify.o src/self_pipe.o

MAKEFILE_PATH := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))

Expand Down
4 changes: 3 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ executable('conmon',
'src/utils.c',
'src/utils.h',
'src/seccomp_notify.c',
'src/seccomp_notify.h'],
'src/seccomp_notify.h',
'src/self_pipe.c',
'src/self_pipe.h'],
dependencies : [glib, libdl, sd_journal, seccomp],
install : true,
install_dir : get_option('bindir'),
Expand Down
10 changes: 10 additions & 0 deletions src/conmon.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "close_fds.h"
#include "seccomp_notify.h"
#include "runtime_args.h"
#include "self_pipe.h"

#include <sys/stat.h>
#include <locale.h>
Expand Down Expand Up @@ -322,6 +323,12 @@ int main(int argc, char *argv[])
pexit("Failed to create signalfd");
int signal_fd_tag = g_unix_fd_add(signal_fd, G_IO_IN, on_signalfd_cb, &data);

/* Create a self-pipe to safely wake up the main loop from signal handlers.
* This avoids calling raise() from a signal handler while ppoll() is active,
* which can trigger glibc's __syscall_cancel and cause SIGABRT (issue #657). */
if (self_pipe_init(self_pipe_cb, &data) < 0)
pexit("Failed to create self-pipe");

if (opt_exit_command)
atexit(do_exit_command);

Expand Down Expand Up @@ -501,6 +508,9 @@ int main(int argc, char *argv[])
g_source_remove(signal_fd_tag);
close(signal_fd);

/* Clean up the self-pipe */
self_pipe_fini();

/*
* Podman injects some fd's into the conmon process so that exposed ports are kept busy while
* the container runs. Close them before we notify the container exited, so that they can be
Expand Down
27 changes: 25 additions & 2 deletions src/ctr_exit.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "ctr_logging.h"
#include "close_fds.h"
#include "oom.h"
#include "self_pipe.h"

#include <errno.h>
#include <glib.h>
Expand All @@ -33,8 +34,11 @@ void on_sig_exit(int signal)
return;
}
}
/* Just force a check if we get here. */
raise(SIGUSR1);
/* Just force a check if we get here.
* Use the self-pipe trick to safely wake up the GLib main loop.
* Calling raise() from a signal handler while the main thread is in ppoll()
* can trigger glibc's __syscall_cancel mechanism, causing SIGABRT (issue #657). */
self_pipe_wake();
}

static void check_child_processes(GHashTable *pid_to_handler, GHashTable *cache)
Expand Down Expand Up @@ -108,6 +112,25 @@ gboolean on_signalfd_cb(gint fd, G_GNUC_UNUSED GIOCondition condition, gpointer
return G_SOURCE_CONTINUE;
}

/* Callback for self-pipe: drain all bytes and check child processes.
* This is used to safely wake up the main loop from signal handlers
* without calling raise() which can trigger glibc __syscall_cancel (issue #657). */
gboolean self_pipe_cb(G_GNUC_UNUSED gint fd, G_GNUC_UNUSED GIOCondition condition, gpointer user_data)
{
struct pid_check_data *data = (struct pid_check_data *)user_data;

/* Drain all bytes from the pipe to avoid flooding the event loop. */
char buf[256];
for (;;) {
ssize_t r = read(fd, buf, sizeof(buf));
if (r <= 0)
break;
}

check_child_processes(data->pid_to_handler, data->exit_status_cache);
return G_SOURCE_CONTINUE;
}

gboolean timeout_cb(G_GNUC_UNUSED gpointer user_data)
{
timed_out = TRUE;
Expand Down
1 change: 1 addition & 0 deletions src/ctr_exit.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ void on_sig_exit(int signal);
void container_exit_cb(G_GNUC_UNUSED GPid pid, int status, G_GNUC_UNUSED gpointer user_data);
gboolean check_child_processes_cb(gpointer user_data);
gboolean on_signalfd_cb(gint fd, GIOCondition condition, gpointer user_data);
gboolean self_pipe_cb(gint fd, GIOCondition condition, gpointer user_data);
gboolean timeout_cb(G_GNUC_UNUSED gpointer user_data);
int get_exit_status(int status);
void runtime_exit_cb(G_GNUC_UNUSED GPid pid, int status, G_GNUC_UNUSED gpointer user_data);
Expand Down
2 changes: 2 additions & 0 deletions src/globals.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ int dev_null_w = -1;
gboolean timed_out = FALSE;

GMainLoop *main_loop = NULL;

int self_pipe_w = -1;
2 changes: 2 additions & 0 deletions src/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@ extern gboolean timed_out;

extern GMainLoop *main_loop;

/* Self-pipe for safely waking the main loop from signal handlers */
extern int self_pipe_w;

#endif // GLOBALS_H
70 changes: 70 additions & 0 deletions src/self_pipe.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#define _GNU_SOURCE

#include "self_pipe.h"
#include "globals.h" // self_pipe_w

#include <errno.h>
#include <fcntl.h>
#include <glib-unix.h>
#include <unistd.h>

/* GLib source tag for the self-pipe read-end watcher. */
static int self_pipe_tag = -1;
/* Read end of the self-pipe (for cleanup). */
static int self_pipe_r_fd = -1;

/*
* Initialize the self-pipe mechanism.
* Creates a non-blocking, close-on-exec pipe and registers a GLib IO source
* on the read end. The write end is stored in the global self_pipe_w.
*/
int self_pipe_init(gboolean (*callback)(gint fd, GIOCondition condition, gpointer user_data), gpointer user_data)
{
int pipefd[2];

if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) < 0) {
return -1;
}

self_pipe_w = pipefd[1];
self_pipe_r_fd = pipefd[0];
self_pipe_tag = g_unix_fd_add(self_pipe_r_fd, G_IO_IN, callback, user_data);
return 0;
}

/*
* Clean up the self-pipe: remove the GLib source and close both ends.
*/
void self_pipe_fini(void)
{
if (self_pipe_tag >= 0) {
g_source_remove(self_pipe_tag);
self_pipe_tag = -1;
}
if (self_pipe_r_fd >= 0) {
close(self_pipe_r_fd);
self_pipe_r_fd = -1;
}
if (self_pipe_w >= 0) {
close(self_pipe_w);
self_pipe_w = -1;
}
}

/*
* Wake up the GLib main loop by writing a byte to the self-pipe.
* This function is safe to call from a signal handler (async-signal-safe).
* errno is preserved around the write() since signal handlers must not
* clobber it (POSIX section 2.4.3).
* The write() return value is intentionally ignored: if the pipe buffer is
* full, a previous wake is already pending and will drain soon enough.
*/
void self_pipe_wake(void)
{
if (self_pipe_w >= 0) {
int saved_errno = errno;
char c = 'x';
gssize ret __attribute__((unused)) = write(self_pipe_w, &c, 1);
errno = saved_errno;
}
}
35 changes: 35 additions & 0 deletions src/self_pipe.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#if !defined(SELF_PIPE_H)
#define SELF_PIPE_H

/*
* Self-pipe helper module for safely waking up the GLib main loop
* from signal handlers.
*
* This avoids calling raise() from a signal handler while the main thread
* is in ppoll(), which can trigger glibc's __syscall_cancel mechanism
* and cause SIGABRT (issue #657).
*
* Usage:
* 1. Call self_pipe_init(fd, callback, user_data) during startup to create
* the pipe and register a GLib IO source on the read end.
* 2. Call self_pipe_wake() from any signal handler to wake the main loop.
* 3. The callback receives (fd, condition, user_data) and must drain all
* bytes from the pipe before returning.
*/

#include <glib.h>

/* Initialize the self-pipe: create pipe2 with O_CLOEXEC|O_NONBLOCK,
* register GLib IO source on read end. Returns 0 on success, -1 on failure. */
int self_pipe_init(gboolean (*callback)(gint fd, GIOCondition condition, gpointer user_data), gpointer user_data);

/* Write a single byte to the self-pipe to wake up the main loop.
* Safe to call from a signal handler (async-signal-safe).
* errno is preserved around the write(). */
void self_pipe_wake(void);

/* Clean up the self-pipe: remove the GLib source and close both ends.
* Call this before exiting to avoid resource leaks. */
void self_pipe_fini(void);

#endif // SELF_PIPE_H