Skip to content

fix(libc): guard hash_page sigprocmask use behind _NO_SIGPROCMASK#12

Open
esaurez wants to merge 1 commit into
devfrom
fix/libc-hash-page-sig-mask-guard
Open

fix(libc): guard hash_page sigprocmask use behind _NO_SIGPROCMASK#12
esaurez wants to merge 1 commit into
devfrom
fix/libc-hash-page-sig-mask-guard

Conversation

@esaurez
Copy link
Copy Markdown

@esaurez esaurez commented May 30, 2026

What

Guard the sigprocmask/sigfillset calls in
newlib/libc/search/hash_page.c::open_temp() behind a new
_NO_SIGPROCMASK target opt-out macro, and enable that opt-out for
*-nanvix* in newlib/configure.host.

Why

open_temp() brackets a mkstemp + unlink window in a signal-mask
block:

sigset_t set, oset;
(void)sigfillset(&set);
(void)sigprocmask(SIG_BLOCK, &set, &oset);
if ((hashp->fp = mkstemp(namestr)) != -1) {
    (void)unlink(namestr);
#ifdef HAVE_FCNTL
    (void)fcntl(hashp->fp, F_SETFD, 1);
#endif
}
(void)sigprocmask(SIG_SETMASK, &oset, (sigset_t *)NULL);

The sigprocmask and sigfillset calls are unconditional. The nanvix
newlib port does not provide a sigprocmask implementation, so linking
libc.a with --whole-archive (as cpython and several other consumers
do) fails with undefined references to sigprocmask and sigfillset.

The original <sys/signal.h> declarations and the SIG_BLOCK /
SIG_SETMASK macros are all gated together by #if __POSIX_VISIBLE,
which is the default visibility level — so simply testing the macros
is not a reliable proxy for "the symbol is linkable on this target."

How

Introduce _NO_SIGPROCMASK as a target opt-out macro, matching the
existing family already established in newlib/configure.host:

  • _NO_SIGSET — used by newlib/libc/unix/sigset.c
  • _NO_POPEN — same opt-out style
  • _NO_POSIX_SPAWN— same opt-out style
  • _NO_WORDEXP, _NO_GETPWENT, _NO_GETLOGIN, _NO_GETUT, _NO_GETPASS

For nanvix, -D_NO_SIGPROCMASK is added to newlib_cflags in the
*-nanvix* section of configure.host. The default code path is
unchanged for every other target.

This mirrors the existing in-function precedent in the same
open_temp() body: the very next line guards an optional libc API
the target may not provide:

#ifdef HAVE_FCNTL
    (void)fcntl(hashp->fp, F_SETFD, 1);
#endif

Same idiom — an optional API gated by a target-provided capability
macro.

Re-enabling the protection

A target that wishes to re-enable the original signal-mask protection
needs both:

  1. Remove -D_NO_SIGPROCMASK from its newlib_cflags entry in
    newlib/configure.host.
  2. Provide a linkable sigprocmask (and sigfillset) implementation
    for the target.

For nanvix specifically that means implementing real signal-mask
support in libposix (or the kernel) and then dropping the opt-out from
configure.host.

Effect of the opt-out

When _NO_SIGPROCMASK is defined, the mkstemp + unlink window is
no longer protected by a blocked signal mask. On a target with no
asynchronous signal delivery, no handler can run inside that window,
so the protection was a no-op in practice. On a target with partial
signal support, a signal interrupting between mkstemp and unlink
could leave the temp file behind — which is exactly why such targets
opt out explicitly.

Related, out of scope

newlib/libc/posix/posix_spawn.c also references sigprocmask
unconditionally; same root cause but a different translation unit.
Will be addressed in a follow-up PR.

The same bug is present in upstream sourceware newlib; landing in the
nanvix fork first.

Verification

Validated with a small Dockerfile.localpatch-hash-page against
ghcr.io/nanvix/toolchain-gcc:sha-34a3641 using unifdef to simulate
the opt-out and the default builds:

Config sigprocmask( present sigfillset( present Result
-D_NO_SIGPROCMASK no no OK
default (no opt-out) yes yes OK

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

newlib/libc/search/hash_page.c::open_temp() unconditionally calls sigprocmask() and sigfillset() around a mkstemp + unlink window. Targets whose newlib build does not provide sigprocmask (e.g. nanvix) therefore fail to link when libc.a is pulled in with --whole-archive: undefined references to sigprocmask and sigfillset.

Introduce _NO_SIGPROCMASK as a target opt-out, matching the existing _NO_SIGSET / _NO_POPEN / _NO_POSIX_SPAWN family already used by newlib/configure.host. The default code path is unchanged; targets that lack sigprocmask add -D_NO_SIGPROCMASK to newlib_cflags and the masking step is compiled out.

This mirrors the in-function HAVE_FCNTL guard around fcntl(F_SETFD, ...) on the next line: an optional libc API the target may or may not provide is gated by a capability macro defined per target in configure.host.

Effect of the opt-out: the mkstemp + unlink window is no longer protected by a blocked signal mask. On a target with no asynchronous signal delivery, no handler can run inside that window, so the protection was a no-op in practice.

configure.host is updated only for *-nanvix*; other targets see no behavior change.

Related, not fixed in this PR: newlib/libc/posix/posix_spawn.c also references sigprocmask unconditionally; same root cause, separate fix.

Verification: preprocessor + unifdef gating test under ghcr.io/nanvix/toolchain-gcc:sha-34a3641 confirms that with -D_NO_SIGPROCMASK the sigprocmask/sigfillset calls are elided, and that without the macro they remain present (default behavior preserved for all other targets).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant