When built with GCC 16 and -O2 -flto, and with -rdynamic stripped out of configure (*), bash hangs in stop_pipeline when iterating through newjob->pipe->next.
```
^C
Program received signal SIGINT, Interrupt.
0x0000555555591ca6 in stop_pipeline.constprop.0 (async=async@entry=0,
deferred=0x0) at /home/sam/git/bash/jobs.c:644
644 for (p = newjob->pipe; p->next; p = p->next)
(gdb) bt
#0 0x0000555555591ca6 in stop_pipeline.constprop.0 (async=async@entry=0,
deferred=0x0) at /home/sam/git/bash/jobs.c:644
#1 0x0000555555577e6b in execute_command_internal (command=0x555555695090,
asynchronous=<optimized out>, pipe_in=-1, pipe_out=-1, fds_to_close=<optimized
out>) at /home/sam/git/bash/execute_cmd.c:963
#2 0x00005555555e5061 in parse_and_execute (string=<optimized out>,
from_file=from_file@entry=0x555555692650 "/home/sam/.bashrc",
flags=flags@entry=1044) at builtins/evalstring.c:567
#3 0x00005555555e5c96 in evalfile_internal
(filename=filename@entry=0x555555692650 "/home/sam/.bashrc",
flags=flags@entry=521) at builtins/evalfile.c:302
#4 0x00005555555e5eec in maybe_execute_file.constprop.0 (fname=<optimized
out>, force_noninteractive=1) at builtins/evalfile.c:348
#5 0x0000555555557879 in run_startup_files () at
/home/sam/git/bash/shell.c:1236
#6 main (argc=1, argv=0x7fffffffd6e8, env=0x7fffffffd6f8) at
/home/sam/git/bash/shell.c:722
```
```
(gdb) p p->next
$1 = (struct process *) 0x555555692050
(gdb) p p->next->next
$2 = (struct process *) 0x555555692050
(gdb) p p->next->next->next
$3 = (struct process *) 0x555555692050
```
In jobs.c [0]:
```
/* Add the current pipeline to the job list. */
if (the_pipeline)
{
register PROCESS *p;
int any_running, any_stopped, n;
newjob = (JOB *)xmalloc (sizeof (JOB));
for (n = 1, p = the_pipeline; p->next != the_pipeline; n++, p = p->next)
;
p->next = (PROCESS *)NULL;
newjob->pipe = REVERSE_LIST (the_pipeline, PROCESS *);
for (p = newjob->pipe; p->next; p = p->next)
```
When bash works, p->next is just NULL.
The issue is REVERSE_LIST [1] calls list_reverse [2] which reverses the
linked list while doing accesses as GENERIC_LIST* [3], but the original
elements are PROCESS*.
I think GCC has an exemption for doing this with void* but even that
isn't required to work by the standard. I think it either needs to be
done with char* or with GENERIC_LIST* marked with
__attribute__((may_alias)) (though that isn't portable). I've attached
the trivial patch to do the latter which I'm using, as it works fine for
our environments, but it's not suitable as-is for bash upstream.
[0] jobs.c#L643 (637f5c8696a6adc9b4519f1cd74aa78492266b7f)
[1] general.h#L135 (637f5c8696a6adc9b4519f1cd74aa78492266b7f)
[2] list.c#L55 (637f5c8696a6adc9b4519f1cd74aa78492266b7f)
[3] general.h#L123 (637f5c8696a6adc9b4519f1cd74aa78492266b7f)
(*) We've done this since before my time in Gentoo when the user
disables plugin support. GCC has more freedom with it removed which is
how this bug happens.
thanks,
sam
From 7309ee9f263dfcaf11eeca679cfec5cf5c8f9455 Mon Sep 17 00:00:00 2001 Message-ID: <7309ee9f263dfcaf11eeca679cfec5cf5c8f9455.1774896688.git....@gentoo.org> From: Sam James <[email protected]> Date: Mon, 30 Mar 2026 19:19:33 +0100 Subject: [PATCH] general: workaround aliasing violation in REVERSE_LIST macro In jobs.c, say, we have: ``` p->next = (PROCESS *)NULL; newjob->pipe = REVERSE_LIST (the_pipeline, PROCESS *); for (p = newjob->pipe; p->next; p = p->next) ``` REVERSE_LIST (-> list_reverse) reverses the linked list `the_pipeline` while doing accesses as GENERIC_LIST*, but the original elements are PROCESS*. I think GCC has an exemption for doing this with void* but even that isn't required to work by the standard. I think it either needs to be done with char* or with GENERIC_LIST marked with __attribute__((may_alias)) (though that isn't portable). For Gentoo, the alias approach is fine until this gets fixed upstream, so do that. This fixes bash being miscompiled by GCC 16 with -O2 -flto and USE=-plugins (for -rdynamic to be dropped). Bug: https://bugs.gentoo.org/971782 Signed-off-by: Sam James <[email protected]> --- general.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/general.h b/general.h index 5b1eac08..883939a7 100644 --- general.h +++ general.h @@ -122,7 +122,7 @@ extern char *strcpy (char *, const char *); can be written to handle the general case for linked lists. */ typedef struct g_list { struct g_list *next; -} GENERIC_LIST; +} __attribute__((may_alias)) GENERIC_LIST; /* Here is a generic structure for associating character strings with integers. It is used in the parser for shell tokenization. */ base-commit: 637f5c8696a6adc9b4519f1cd74aa78492266b7f -- 2.53.0
signature.asc
Description: PGP signature
