From ee03ec42c4dcd21fcbd3c3bd9d804b9d5045fe9d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 18 Aug 2026 20:58:42 +0400 Subject: [PATCH] Core: make channel file descriptor errors non-fatal If a worker process' file descriptor table is full, recvmsg() is not able to read out the channel fd sent from the master. This results in different behavior on different platforms. On Linux, recvmsg() succeeds, returns the payload and drops the fd. On BSD systems, the first recvmsg() call fails with EMSGSIZE/EMFILE, but the next call succeeds and returns the payload without the fd. On Solaris, recvmsg() fails with EMFILE and drops both the payload and the fd. Previously, all of these paths resulted in fatal errors. The worker channel was closed and the worker was no longer capable of receiving messages from the master. In particular, the NGX_CMD_QUIT command could not be delivered anymore. As a result, the worker never exited after nginx reload and kept running and accepting new connections forever until killed by a signal. Fix is to consider those errors non-fatal. An additional check is added to the NGX_CMD_CLOSE_CHANNEL handler to make sure the worker has received a file descriptor for this entry before. Also, the MSG_CTRUNC check is removed since a proper ancillary data size validation is now implemented. --- src/os/unix/ngx_channel.c | 34 +++++++++++++++++++++------------ src/os/unix/ngx_process_cycle.c | 6 ++++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/os/unix/ngx_channel.c b/src/os/unix/ngx_channel.c index 1efa0667c..b03bbbb15 100644 --- a/src/os/unix/ngx_channel.c +++ b/src/os/unix/ngx_channel.c @@ -134,6 +134,13 @@ ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log) } ngx_log_error(NGX_LOG_ALERT, log, err, "recvmsg() failed"); + + if (err == NGX_EMSGSIZE || err == NGX_EMFILE) { + /* file descriptor table is full */ + ngx_memzero(ch, size); + return 0; + } + return NGX_ERROR; } @@ -152,27 +159,29 @@ ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log) if (ch->command == NGX_CMD_OPEN_CHANNEL) { - if (cmsg.cm.cmsg_len < (socklen_t) CMSG_LEN(sizeof(int))) { + if (msg.msg_controllen < (socklen_t) CMSG_LEN(sizeof(int))) { ngx_log_error(NGX_LOG_ALERT, log, 0, "recvmsg() returned too small ancillary data"); - return NGX_ERROR; - } + ch->fd = -1; - if (cmsg.cm.cmsg_level != SOL_SOCKET || cmsg.cm.cmsg_type != SCM_RIGHTS) + } else if (cmsg.cm.cmsg_level != SOL_SOCKET + || cmsg.cm.cmsg_type != SCM_RIGHTS) { ngx_log_error(NGX_LOG_ALERT, log, 0, "recvmsg() returned invalid ancillary data " "level %d or type %d", cmsg.cm.cmsg_level, cmsg.cm.cmsg_type); return NGX_ERROR; + + } else { + + /* ch->fd = *(int *) CMSG_DATA(&cmsg.cm); */ + + ngx_memcpy(&ch->fd, CMSG_DATA(&cmsg.cm), sizeof(int)); } - - /* ch->fd = *(int *) CMSG_DATA(&cmsg.cm); */ - - ngx_memcpy(&ch->fd, CMSG_DATA(&cmsg.cm), sizeof(int)); } - if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { + if (msg.msg_flags & MSG_TRUNC) { ngx_log_error(NGX_LOG_ALERT, log, 0, "recvmsg() truncated data"); } @@ -183,10 +192,11 @@ ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log) if (msg.msg_accrightslen != sizeof(int)) { ngx_log_error(NGX_LOG_ALERT, log, 0, "recvmsg() returned no ancillary data"); - return NGX_ERROR; - } + ch->fd = -1; - ch->fd = fd; + } else { + ch->fd = fd; + } } #endif diff --git a/src/os/unix/ngx_process_cycle.c b/src/os/unix/ngx_process_cycle.c index 5bc5ce979..33c25487b 100644 --- a/src/os/unix/ngx_process_cycle.c +++ b/src/os/unix/ngx_process_cycle.c @@ -1073,6 +1073,12 @@ ngx_channel_handler(ngx_event_t *ev) ch.slot, ch.pid, ngx_processes[ch.slot].pid, ngx_processes[ch.slot].channel[0]); + if (ngx_processes[ch.slot].channel[0] == -1 + || ngx_processes[ch.slot].pid != ch.pid) + { + break; + } + if (close(ngx_processes[ch.slot].channel[0]) == -1) { ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, "close() channel failed");