FTBFS: arm64, riscv64: ‘read’ writing 1 byte into a region of size 0 overflows the destination

Bug #2053134 reported by Andreas Hasenack
10
This bug affects 1 person
Affects Status Importance Assigned to Milestone
GLibC
Fix Released
Medium
glibc (Ubuntu)
New
Undecided
Unassigned
tgt (Ubuntu)
Fix Released
High
Andreas Hasenack

Bug Description

Log from arm64:

cc -Wdate-time -D_FORTIFY_SOURCE=3 -c -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=/home/ubuntu/git/packages/tgt/tgt=. -flto=auto -ffat-lto-objects -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -mbranch-protection=standard -fdebug-prefix-map=/home/ubuntu/git/packages/tgt/tgt=/usr/src/tgt-1:1.0.85-1.1ubuntu1 -DUSE_SIGNALFD -DUSE_TIMERFD -DHAVE_GFAPI_VER_7_6 -DUSE_SYSTEMD -DUSE_EVENTFD -D_GNU_SOURCE -I. -g -O2 -fno-strict-aliasing -Wall -Wstrict-prototypes -Werror -DTGT_VERSION=\"1.0.85\" -DBSDIR=\"/usr/lib/tgt/backing-store\" bs_sheepdog.c -o bs_sheepdog.o
bs.c: In function ‘bs_sig_request_done’:
bs.c:196:15: error: ‘read’ writing 1 byte into a region of size 0 overflows the destination [-Werror=stringop-overflow=]
  196 | ret = read(fd, (char *)siginfo, sizeof(siginfo));
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bs.c:193:33: note: destination object ‘siginfo’ of size 0
  193 | struct signalfd_siginfo siginfo[16];
      | ^~~~~~~
In file included from /usr/include/unistd.h:1217,
                 from bs.c:33:
/usr/include/aarch64-linux-gnu/bits/unistd.h:26:1: note: in a call to function ‘read’ declared with attribute ‘access (write_only, 2)’
   26 | read (int __fd, void *__buf, size_t __nbytes)
      | ^~~~
cc -Wdate-time -D_FORTIFY_SOURCE=3 -c -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=/home/ubuntu/git/packages/tgt/tgt=. -flto=auto -ffat-lto-objects -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -mbranch-protection=standard -fdebug-prefix-map=/home/ubuntu/git/packages/tgt/tgt=/usr/src/tgt-1:1.0.85-1.1ubuntu1 -DUSE_SIGNALFD -DUSE_TIMERFD -DHAVE_GFAPI_VER_7_6 -DUSE_SYSTEMD -DUSE_EVENTFD -D_GNU_SOURCE -I. -g -O2 -fno-strict-aliasing -Wall -Wstrict-prototypes -Werror -DTGT_VERSION=\"1.0.85\" -DBSDIR=\"/usr/lib/tgt/backing-store\" tgtadm.c -o tgtadm.o
cc1: all warnings being treated as errors

Tags: ftbfs

Related branches

tags: added: server-todo
Revision history for this message
Andreas Hasenack (ahasenack) wrote :

This is happening on arm64 because __NR_signalfd is not defined there. arm64 only has __NR_signalfd4[1]

The following #ifdef[2] in usr/util.h then fails:

#if defined(__NR_signalfd) && defined(USE_SIGNALFD)
...

And we end up in the #else clause, which:
#else
#define __signalfd(fd, mask, flags) (-1)
struct signalfd_siginfo {
};
#endif

This essentially makes this struct zero, and gcc/glibc is now catching that in usr/bs.c[3]:

 struct signalfd_siginfo siginfo[16];
...
 ret = read(fd, (char *)siginfo, sizeof(siginfo));

sizeof(siginfo) is zero, so nothing should be written, but this is tripping the -Wstringop-overflow check.

I also wonder if we are incorrectly building tgt without signalfd support for all this time, unknowingly, because of this...

1. https://github.com/bminor/glibc/blob/f9ac84f92f151e07586c55e14ed628d493a5929d/sysdeps/unix/sysv/linux/aarch64/arch-syscall.h#L255

2. https://github.com/fujita/tgt/blob/master/usr/util.h#L106

3. https://github.com/fujita/tgt/blob/master/usr/bs.c#L196

Revision history for this message
Andreas Hasenack (ahasenack) wrote :

I think this is a false positive. Here is a quick reproducer on amd64:
#include <stdio.h>
#include <unistd.h>

int main(void) {
    struct test_st {};
    int fd = 0;
    int count = 0;

    struct test_st test_info[16];

    printf("Hello world\n");
    printf("sizeof struct test_st: %ld\n", sizeof(struct test_st));
    printf("sizeof test_info[16]: %ld\n", sizeof(test_info));
    count = read(fd, test_info, sizeof(test_info)); /* invalid fd, don't care */
    printf("count is %d\n", count);
    return(0);
}

When this is built with -D_FORTIFY_SOURCE=3 *and* -O2 (or -O1 even), it issues the same warning:
$ gcc foo.c -o foo -D_FORTIFY_SOURCE=3 -O2
<command-line>: warning: "_FORTIFY_SOURCE" redefined
<built-in>: note: this is the location of the previous definition
foo.c: In function ‘main’:
foo.c:14:13: warning: ‘read’ writing 1 byte into a region of size 0 overflows the destination [-Wstringop-overflow=]
   14 | count = read(fd, test_info, sizeof(test_info));
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
foo.c:9:20: note: destination object ‘test_info’ of size 0
    9 | struct test_st test_info[16];
      | ^~~~~~~~~
In file included from /usr/include/unistd.h:1214,
                 from foo.c:2:
/usr/include/x86_64-linux-gnu/bits/unistd.h:26:1: note: in a call to function ‘read’ declared with attribute ‘access (write_only, 2)’
   26 | read (int __fd, void *__buf, size_t __nbytes)
      | ^~~~

But note that read(2) with a count of zero will not overflow the target, as nothing will be read, so this seems like a bug in gcc. And indeed, there are several[1] such bug reports in gcc's bugzilla. Where it got the "1 byte" I don't know.

1. https://gcc.gnu.org/bugzilla/buglist.cgi?quicksearch=writing%201%20byte%20into%20a%20region%20of%20size%200

Revision history for this message
Andreas Hasenack (ahasenack) wrote :
Revision history for this message
In , Pinskia (pinskia) wrote :

__fortified_attr_access seems to be defined incorrectly for _FORTIFY_SOURCE==3.
The documentation for the size-index of access attribute (https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-access-function-attribute) has the following:
```
When no size-index argument is specified, the pointer argument must be either null or point to a space that is suitably aligned and large for __at least one object__ of the referenced type (this implies that a past-the-end pointer is not a valid argument).
```

Notice the __at least__ part here. That means the definition of __fortified_attr_access is wrong when _FORTIFY_SOURCE==3, when passing around 0 size structs.

An example is:
```

#include <stdio.h>
#include <unistd.h>

int main(void) {
    struct test_st {};
    int fd = 0;
    int count = 0;

    struct test_st test_info[16];

    count = read(fd, test_info, sizeof(test_info));
    return(0);
}
```

With _FORTIFY_SOURCE==3 we get:
 __attribute__ ((__access__ (__write_only__, 2)))

Which means the size has to be at least 1 but test_info has size of 0 and we are passing a size of 0 to read even.

This is moved from GCC bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113922 .

Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

It turns out that this is probably a glibc bug:

https://sourceware.org/bugzilla/show_bug.cgi?id=31383

Changed in glibc:
importance: Unknown → Medium
status: Unknown → Confirmed
Revision history for this message
Simon Chopin (schopin) wrote :

Just to be clear, which version of glibc are we talking about here, the one in noble-proposed or the one in the release pocket?

Revision history for this message
Andreas Hasenack (ahasenack) wrote (last edit ):

I confirmed the ftbfs with noble-release as well.

The reproducer from comment #2 happens also in my mantic machine.

Revision history for this message
In , Siddhesh-n (siddhesh-n) wrote :

```
When no size-index argument is specified, the pointer argument must be either null or point to a space that is suitably aligned and large for __at least one object__ of the referenced type (this implies that a past-the-end pointer is not a valid argument).
```

Well technically, the pointer argument *is* suitably aligned and large for 16 objects of 0 size, but the implication that it is hence not a past-the-end pointer is invalid. I'll drop the access attribute (since the additional value from having it is not really significant enough) but IMO -Wstringop-overflow needs to be fixed to handle pointers to zero-sized structs, i.e. it needs to ignore them and not conjure up an access size of 1 out of nowhere.

Revision history for this message
Andreas Hasenack (ahasenack) wrote :

tgt upstream replied[1] with a patch dropping the workaround which triggered this glibc/gcc bug.

1. https://www.spinics.net/lists/linux-stgt/msg04784.html

Revision history for this message
Gianfranco Costamagna (costamagnagianfranco) wrote :

without patch: (noble-release)
root@Unimatrix08-Jammy:/# tgtd -f
tgtd: iser_ib_init(3431) Failed to initialize RDMA; load kernel modules?
tgtd: work_timer_start(146) use timer_fd based scheduler
tgtd: bs_init(393) use pthread notification

with upstream patch:
root@Unimatrix08-Jammy:/# tgtd -f
tgtd: iser_ib_init(3431) Failed to initialize RDMA; load kernel modules?
tgtd: work_timer_start(146) use timer_fd based scheduler
tgtd: bs_init(387) use signalfd notification

Revision history for this message
Gianfranco Costamagna (costamagnagianfranco) wrote :

Description:
bs.c: In function ‘bs_sig_request_done’:
bs.c:196:15: error: ‘read’ writing 1 byte into a region of size 0 overflows the destination [-Werror=stringop-overflow=]
  196 | ret = read(fd, (char *)siginfo, sizeof(siginfo));
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bs.c:193:33: note: destination object ‘siginfo’ of size 0
  193 | struct signalfd_siginfo siginfo[16];
      | ^~~~~~~
In file included from /usr/include/unistd.h:1217,
                 from bs.c:33:
/usr/include/aarch64-linux-gnu/bits/unistd.h:26:1: note: in a call to function ‘read’ declared with attribute ‘access (write_only, 2)’
   26 | read (int __fd, void *__buf, size_t __nbytes)
      | ^~~~
Origin: Upstream https://www.spinics.net/lists/linux-stgt/msg04784.html
Last-Update: 2024-02-15

diff --git a/usr/bs.c b/usr/bs.c
index 8171a32..8da5a9b 100644
--- a/usr/bs.c
+++ b/usr/bs.c
@@ -311,7 +311,7 @@ static int bs_init_signalfd(void)
  sigaddset(&mask, SIGUSR2);
  sigprocmask(SIG_BLOCK, &mask, NULL);

- sig_fd = __signalfd(-1, &mask, 0);
+ sig_fd = signalfd(-1, &mask, 0);
  if (sig_fd < 0)
   return 1;

diff --git a/usr/util.h b/usr/util.h
index c709f9b..8aef6ab 100644
--- a/usr/util.h
+++ b/usr/util.h
@@ -14,6 +14,7 @@
 #include <string.h>
 #include <limits.h>
 #include <linux/types.h>
+#include <sys/signalfd.h>

 #include "be_byteshift.h"

@@ -99,44 +101,6 @@ static inline int between(uint32_t seq1, uint32_t seq2, uint32_t seq3)

 extern unsigned long pagesize, pageshift;

-#if defined(__NR_signalfd) && defined(USE_SIGNALFD)
-
-/*
- * workaround for broken linux/signalfd.h including
- * usr/include/linux/fcntl.h
- */
-#define _LINUX_FCNTL_H
-
-#include <linux/signalfd.h>
-
-static inline int __signalfd(int fd, const sigset_t *mask, int flags)
-{
- int fd2, ret;
-
- fd2 = syscall(__NR_signalfd, fd, mask, _NSIG / 8);
- if (fd2 < 0)
- return fd2;
-
- ret = fcntl(fd2, F_GETFL);
- if (ret < 0) {
- close(fd2);
- return -1;
- }
-
- ret = fcntl(fd2, F_SETFL, ret | O_NONBLOCK);
- if (ret < 0) {
- close(fd2);
- return -1;
- }
-
- return fd2;
-}
-#else
-#define __signalfd(fd, mask, flags) (-1)
-struct signalfd_siginfo {
-};
-#endif
-
 /* convert string to integer, check for validity of the string numeric format
  * and the natural boundaries of the integer value type (first get a 64-bit
  * value and check that it fits the range of the destination integer).

Changed in tgt (Ubuntu):
status: In Progress → Fix Released
Revision history for this message
Gianfranco Costamagna (costamagnagianfranco) wrote :

(due to an error in the closing bug syntax, this bug has been manually closed!)

Revision history for this message
In , Siddhesh-n (siddhesh-n) wrote :

Patch posted: https://patchwork.sourceware<email address hidden>/

Revision history for this message
In , Siddhesh-n (siddhesh-n) wrote :

(In reply to Siddhesh Poyarekar from comment #1)
> ```
> When no size-index argument is specified, the pointer argument must be
> either null or point to a space that is suitably aligned and large for __at
> least one object__ of the referenced type (this implies that a past-the-end
> pointer is not a valid argument).
> ```
>
> Well technically, the pointer argument *is* suitably aligned and large for
> 16 objects of 0 size, but the implication that it is hence not a
> past-the-end pointer is invalid. I'll drop the access attribute (since the
> additional value from having it is not really significant enough) but IMO
> -Wstringop-overflow needs to be fixed to handle pointers to zero-sized
> structs, i.e. it needs to ignore them and not conjure up an access size of 1
> out of nowhere.

Actually, no, I was wrong. The referenced type is void*, which is why the warning is 'correct'. Maybe there's scope for better wording, but it does make sense to warn in such cases:

extern void f2 (void *) __attribute__ ((__access__ (__write_only__, 1)));

void
f1 (void)
{
  struct A {} a[16];

  f2 (a);
}

Changed in glibc:
status: Confirmed → In Progress
Revision history for this message
In , Cvs-commit (cvs-commit) wrote :

The master branch has been updated by Siddhesh Poyarekar <email address hidden>:

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=bf9688e623262c5fa9f91e4de0e84db45025076f

commit bf9688e623262c5fa9f91e4de0e84db45025076f
Author: Siddhesh Poyarekar <email address hidden>
Date: Thu Feb 15 07:40:56 2024 -0500

    cdefs: Drop access attribute for _FORTIFY_SOURCE=3 (BZ #31383)

    When passed a pointer to a zero-sized struct, the access attribute
    without the third argument misleads -Wstringop-overflow diagnostics to
    think that a function is writing 1 byte into the zero-sized structs.
    The attribute doesn't add that much value in this context, so drop it
    completely for _FORTIFY_SOURCE=3.

    Resolves: BZ #31383
    Signed-off-by: Siddhesh Poyarekar <email address hidden>
    Reviewed-by: Adhemerval Zanella <email address hidden>

Revision history for this message
In , Siddhesh-n (siddhesh-n) wrote :

Done.

Changed in glibc:
status: In Progress → Fix Released
Bryce Harrington (bryce)
tags: removed: server-todo
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.