Bug 30584 - initstate and initstate_r crash when the state argument is unaligned
Summary: initstate and initstate_r crash when the state argument is unaligned
Status: RESOLVED FIXED
Alias: None
Product: glibc
Classification: Unclassified
Component: libc (show other bugs)
Version: 2.36
: P2 normal
Target Milestone: 2.41
Assignee: Sam James
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2023-06-25 15:52 UTC by Bruno Haible
Modified: 2025-01-02 16:52 UTC (History)
5 users (show)

See Also:
Host: sparc64-unknown-linux-gnu
Target:
Build: sparc64-unknown-linux-gnu
Last reconfirmed: 2023-06-25 00:00:00


Attachments
test case foo1.c (188 bytes, text/x-csrc)
2023-06-25 15:52 UTC, Bruno Haible
Details
test case foo2.c (135 bytes, text/x-csrc)
2023-06-25 15:52 UTC, Bruno Haible
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Bruno Haible 2023-06-25 15:52:14 UTC
Created attachment 14947 [details]
test case foo1.c

On Linux/SPARC (both in 64-bit mode and in 32-bit mode), the initstate() and initstate_r() functions crash when the state argument is unaligned.

The specification of initstate() in https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html and the documentation of initstate() and initstate_r() in https://www.gnu.org/software/libc/manual/html_node/BSD-Random.html require merely a byte array. So, passing an aligned byte array (of size 128 or more) is allowed.

How to reproduce:
Remote login to cfarm105.cfarm.net.

$ gcc -Wall foo1.c
$ ./a.out
Bus error
$ gcc -m32 -Wall foo1.c
$ ./a.out
Bus error

$ gcc -Wall foo2.c
$ ./a.out
Bus error
$ gcc -m32 -Wall foo2.c
$ ./a.out
Bus error
Comment 1 Bruno Haible 2023-06-25 15:52:45 UTC
Created attachment 14948 [details]
test case foo2.c
Comment 2 Bruno Haible 2023-06-25 15:54:26 UTC
I meant: Passing an *unaligned* byte array is allowed.
Comment 3 Andreas Schwab 2023-07-04 09:41:18 UTC
This is inherited from BSD, which documents that the state array needs to be uint32_t aligned.
Comment 4 matoro 2024-06-03 22:49:07 UTC
I've just run into this as we're trying to test libunistring-1.2 downstream.  What's the consensus here?
Comment 5 Sam James 2024-10-16 06:39:29 UTC
azanella, would you be kind enough to look? I would be happy to try but https://sourceware.org/bugzilla/show_bug.cgi?id=30584#c3 makes me fear that it's not clear how to fix it at all without breaking ABI.
Comment 6 Adhemerval Zanella 2024-10-16 19:08:30 UTC
(In reply to Sam James from comment #5)
> azanella, would you be kind enough to look? I would be happy to try but
> https://sourceware.org/bugzilla/show_bug.cgi?id=30584#c3 makes me fear that
> it's not clear how to fix it at all without breaking ABI.

I think the way to fix unaligned access without breaking the API is internally use memcpy or packed struct:

diff --git a/stdlib/random_r.c b/stdlib/random_r.c
index b6297fe099..dee3bc9535 100644
--- a/stdlib/random_r.c
+++ b/stdlib/random_r.c
@@ -55,6 +55,7 @@
 #include <limits.h>
 #include <stddef.h>
 #include <stdlib.h>
+#include <string.h>
 
 
 /* An improved random number generation package.  In addition to the standard
@@ -146,7 +147,19 @@ static const struct random_poly_info random_poly_info =
   { DEG_0, DEG_1, DEG_2, DEG_3, DEG_4 }
 };
 
+static inline int32_t
+read_state (int32_t *b, size_t idx)
+{
+  int32_t r;
+  memcpy (&r, &b[idx], sizeof (int32_t));
+  return r;
+}
 
+static inline void
+write_state (int32_t *b, size_t idx, int32_t v)
+{
+  memcpy (&b[idx], &v, sizeof (int32_t));
+}
 
 
 /* Initialize the random number generator based on the given seed.  If the
@@ -177,7 +190,7 @@ __srandom_r (unsigned int seed, struct random_data *buf)
   /* We must make sure the seed is not 0.  Take arbitrarily 1 in this case.  */
   if (seed == 0)
     seed = 1;
-  state[0] = seed;
+  write_state (state, 0, seed);
   if (type == TYPE_0)
     goto done;
 
@@ -194,7 +207,7 @@ __srandom_r (unsigned int seed, struct random_data *buf)
       word = 16807 * lo - 2836 * hi;
       if (word < 0)
 	word += 2147483647;
-      *++dst = word;
+      write_state (++dst, 0, word);
     }
 
   buf->fptr = &state[buf->rand_sep];
@@ -238,9 +251,10 @@ __initstate_r (unsigned int seed, char *arg_state, size_t n,
     {
       int old_type = buf->rand_type;
       if (old_type == TYPE_0)
-	old_state[-1] = TYPE_0;
+	write_state (old_state, -1, TYPE_0);
       else
-	old_state[-1] = (MAX_TYPES * (buf->rptr - old_state)) + old_type;
+	write_state (old_state, -1, (MAX_TYPES * (buf->rptr - old_state))
+				    + old_type);
     }
 
   int type;
@@ -270,9 +284,9 @@ __initstate_r (unsigned int seed, char *arg_state, size_t n,
 
   __srandom_r (seed, buf);
 
-  state[-1] = TYPE_0;
+  write_state (state, -1, TYPE_0);
   if (type != TYPE_0)
-    state[-1] = (buf->rptr - state) * MAX_TYPES + type;
+    write_state (state, -1, (buf->rptr - state) * MAX_TYPES + type);
 
   return 0;
 
@@ -307,9 +321,10 @@ __setstate_r (char *arg_state, struct random_data *buf)
   old_type = buf->rand_type;
   old_state = buf->state;
   if (old_type == TYPE_0)
-    old_state[-1] = TYPE_0;
+    write_state (old_state, -1, TYPE_0);
   else
-    old_state[-1] = (MAX_TYPES * (buf->rptr - old_state)) + old_type;
+    write_state (old_state, -1, (MAX_TYPES * (buf->rptr - old_state))
+				+ old_type);
 
   type = new_state[-1] % MAX_TYPES;
   if (type < TYPE_0 || type > TYPE_4)
@@ -361,8 +376,8 @@ __random_r (struct random_data *buf, int32_t *result)
 
   if (buf->rand_type == TYPE_0)
     {
-      int32_t val = ((state[0] * 1103515245U) + 12345U) & 0x7fffffff;
-      state[0] = val;
+      int32_t val = ((read_state(state, 0) * 1103515245U) + 12345U) & 0x7fffffff;
+      write_state (state, 0, val);
       *result = val;
     }
   else
@@ -372,7 +387,9 @@ __random_r (struct random_data *buf, int32_t *result)
       int32_t *end_ptr = buf->end_ptr;
       uint32_t val;
 
-      val = *fptr += (uint32_t) *rptr;
+      val = read_state (rptr, 0);
+      int32_t t = read_state (fptr, 0);
+      write_state (fptr, 0, t + val);
       /* Chucking least random bit.  */
       *result = val >> 1;
       ++fptr;

On most architecture that supports fast unaligned access, the resulting code won't change.  However, on sparc and others, it has the drawback that the state will be updated byte-per-byte. It would be possible to speed up things by using a temporary buffer and memcpy it back, but I don't think it is worth.
Comment 7 Sam James 2024-10-16 19:19:19 UTC
Oh! That's fine then.
Comment 8 Bruno Haible 2024-10-16 20:00:25 UTC
> I think the way to fix unaligned access without breaking the API is internally use memcpy or packed struct

Nice!

I wanted to suggest to test _STRING_ARCH_unaligned. But _STRING_ARCH_unaligned has already been removed — in favour of memcpy or a packed struct :) Really nice that we can rely on the compiler for such arch dependent things nowadays.
Comment 9 Sam James 2024-10-18 12:01:49 UTC
azanella, do you plan on handling it or am I? I am happy to take it.
Comment 10 Sam James 2024-10-26 00:46:43 UTC
I'll take it.
Comment 11 Sam James 2024-12-10 02:34:24 UTC
I'm just working on adding a testcase. I should be able to submit it soon and hopefully can be in glibc-2.41 (and backportable).
Comment 13 Sam James 2025-01-02 16:52:10 UTC
Fixed for 2.41. Let's see how things go and may backport later on

commit d5bceac99d24af1131b90027dab267e437b65cd1 (HEAD -> master, upstream/master, origin/master, origin/HEAD, sam-30584)
Author: Sam James <sam@gentoo.org>
Date:   Tue Dec 10 01:21:46 2024 +0000

    stdlib: random_r: fix unaligned access in initstate and initstate_r [BZ #30584]

    The initstate{,_r} interfaces are documented in BSD as needing an aligned
    array of 32-bit values, but neither POSIX nor glibc's own documentation
    require it to be aligned. glibc's documentation says it "should" be a power
    of 2, but not must.

    Use memcpy to read and write to `state` to handle such an unaligned
    argument.

    Co-authored-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
    Reviewed-by: Florian Weimer <fweimer@redhat.com>