-
-
Notifications
You must be signed in to change notification settings - Fork 179
Description
getrandom: Incorrect bit-shift causes poor random number distribution
Summary
DragonOS's getrandom syscall has a bit-shift bug in kernel/src/syscall/sys_getrandom.rs:115 that causes poor random number distribution. The code uses offset * 2 instead of offset * 8, which means consecutive bytes share the same bits instead of extracting independent bits from the random value.
Environment
Git Commit ID:
6fc4e370 - fix(link_test): 修复符号链接、硬链接相关系统调用 (#1709)
Full commit: 6fc4e3707fd34f8d72831f5b41702064114314b3
DragonOS Version: 2026-02-01
Test Case
Source Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <stdint.h>
#ifndef __NR_getrandom
#ifdef __x86_64__
#define __NR_getrandom 318
#elif defined(__riscv)
#define __NR_getrandom 278
#elif defined(__aarch64__)
#define __NR_getrandom 384
#else
#define __NR_getrandom 318
#endif
#endif
#ifndef GRND_NONBLOCK
#define GRND_NONBLOCK 0x0001
#endif
#ifndef GRND_RANDOM
#define GRND_RANDOM 0x0002
#endif
#ifndef GRND_INSECURE
#define GRND_INSECURE 0x0004
#endif
static ssize_t sys_getrandom(void *buf, size_t len, unsigned int flags)
{
return syscall(__NR_getrandom, buf, len, flags);
}
// 分析随机数的质量 - 检查字节分布
void analyze_random_quality(uint8_t *buf, size_t len, const char *label)
{
int byte_counts[256] = {0};
size_t i;
printf("\n=== %s ===\n", label);
printf("Buffer size: %zu bytes\n", len);
// 统计每个字节值的出现次数
for (i = 0; i < len; i++) {
byte_counts[buf[i]]++;
}
// 计算唯一字节值数量
int unique_bytes = 0;
int zero_bytes = 0;
int max_count = 0;
int min_count = len;
for (i = 0; i < 256; i++) {
if (byte_counts[i] > 0) {
unique_bytes++;
if (byte_counts[i] > max_count) {
max_count = byte_counts[i];
}
if (byte_counts[i] < min_count) {
min_count = byte_counts[i];
}
}
if (buf[i] == 0) {
zero_bytes++;
}
}
printf("Unique byte values: %d / 256\n", unique_bytes);
printf("Byte count range: %d to %d\n", min_count, max_count);
// 打印前 32 字节的十六进制
printf("First 32 bytes (hex): ");
size_t print_len = len < 32 ? len : 32;
for (i = 0; i < print_len; i++) {
printf("%02x ", buf[i]);
}
printf("\n");
// 检查前 4 个字节是否有明显的位模式问题
if (len >= 4) {
printf("First 4 bytes: 0x%02x 0x%02x 0x%02x 0x%02x\n",
buf[0], buf[1], buf[2], buf[3]);
printf("As 32-bit value: 0x%08x\n", *(uint32_t*)buf);
// 如果 bug 存在 (offset * 2 而不是 offset * 8),
// 相邻的字节会共享相同的位,导致明显的相关性
uint32_t val = *(uint32_t*)buf;
uint8_t b0 = val & 0xFF;
uint8_t b1 = (val >> 8) & 0xFF;
uint8_t b2 = (val >> 16) & 0xFF;
uint8_t b3 = (val >> 24) & 0xFF;
// 正常情况下,这些位之间不应该有简单的关系
// 但如果有 bug (offset * 2),我们可以检测到模式
printf("Byte analysis:\n");
printf(" b0 = 0x%02x (bits: %08b)\n", b0, b0);
printf(" b1 = 0x%02x (bits: %08b)\n", b1, b1);
printf(" b2 = 0x%02x (bits: %08b)\n", b2, b2);
printf(" b3 = 0x%02x (bits: %08b)\n", b3, b3);
// 检查是否有明显的模式 (如果是 offset * 2 的 bug)
// 在这种情况下,某些位会在多个字节中重复出现
int suspicious_pattern = 0;
if ((b0 & 0xC0) == (b1 & 0xC0)) suspicious_pattern++; // 高 2 位
if ((b1 & 0xC0) == (b2 & 0xC0)) suspicious_pattern++;
if ((b2 & 0xC0) == (b3 & 0xC0)) suspicious_pattern++;
if (suspicious_pattern >= 2) {
printf("WARNING: Detected suspicious bit patterns between bytes!\n");
printf("This may indicate the getrandom bit-shift bug (offset * 2 instead of * 8)\n");
}
}
}
int main(void) {
uint8_t buf[1024];
ssize_t ret;
int i;
printf("=== Testing getrandom syscall for bit-shift bug ===\n\n");
// Test 1: Basic functionality
printf("Test 1: Basic getrandom call (256 bytes)\n");
ret = sys_getrandom(buf, 256, 0);
if (ret < 0) {
perror("FAIL: getrandom failed");
return 1;
}
if (ret != 256) {
printf("FAIL: Expected 256 bytes, got %zd\n", ret);
return 1;
}
printf("PASS: getrandom returned %zd bytes\n", ret);
analyze_random_quality(buf, 256, "Random Quality Analysis");
// Test 2: Multiple calls to check for patterns
printf("\nTest 2: Multiple getrandom calls (checking for patterns)\n");
uint8_t samples[10][4];
for (i = 0; i < 10; i++) {
ret = sys_getrandom(samples[i], 4, 0);
if (ret != 4) {
printf("FAIL: Call %d failed\n", i);
return 1;
}
}
printf("10 samples of 4 bytes each:\n");
for (i = 0; i < 10; i++) {
printf(" Sample %d: %02x %02x %02x %02x (0x%08x)\n",
i, samples[i][0], samples[i][1], samples[i][2], samples[i][3],
*(uint32_t*)samples[i]);
}
// Test 3: Large buffer
printf("\nTest 3: Large buffer (1024 bytes)\n");
ret = sys_getrandom(buf, 1024, 0);
if (ret != 1024) {
printf("FAIL: Expected 1024 bytes, got %zd\n", ret);
return 1;
}
printf("PASS: Got %zd bytes\n", ret);
analyze_random_quality(buf, 1024, "Large Buffer Analysis");
// Test 4: Check zero-length buffer
printf("\nTest 4: Zero-length buffer\n");
ret = sys_getrandom(buf, 0, 0);
if (ret != 0) {
printf("FAIL: Expected 0 bytes, got %zd\n", ret);
return 1;
}
printf("PASS: Zero-length returns 0\n");
// Test 5: NULL pointer (should fail with EFAULT)
printf("\nTest 5: NULL pointer\n");
ret = sys_getrandom(NULL, 10, 0);
if (ret < 0 && errno == EFAULT) {
printf("PASS: NULL pointer correctly returns EFAULT\n");
} else {
printf("INFO: NULL pointer behavior: ret=%zd, errno=%d\n", ret, errno);
}
printf("\n=== Test completed ===\n");
printf("Note: Check the 'Byte analysis' section above.\n");
printf("If bytes show suspicious bit patterns (e.g., high 2 bits repeating),\n");
printf("it indicates the getrandom bit-shift bug (offset * 2 instead of * 8).\n");
return 0;
}Compilation
gcc -Wall -O2 -static test_getrandom_bug.c -o test_getrandom_bugExpected Behavior (Linux)
On Linux (Ubuntu 24.04), the getrandom syscall returns cryptographically secure random bytes with proper bit distribution. Each byte should contain independent bits from the random source.
Linux Command:
docker run --rm -v "$(pwd):/dragonos" ubuntu:24.04 bash -c "
cd /dragonos
apt-get update -qq && apt-get install -y gcc
gcc -Wall -O2 -static user/apps/c_unitest/test_getrandom_bug.c -o /tmp/test_getrandom_bug
/tmp/test_getrandom_bug
"Linux Output:
=== Testing getrandom syscall for bit-shift bug ===
Test 1: Basic getrandom call (256 bytes)
PASS: getrandom returned 256 bytes
=== Random Quality Analysis ===
Buffer size: 256 bytes
Unique byte values: 165 / 256
Byte count range: 1 to 5
First 32 bytes (hex): b6 bf 7b 22 0f 8f 8f 7a 73 68 d1 0a 8c 9e 8d 24 8c ab 2b e6 1b ff d5 2e 4e 0e 66 68 3c c8 15 7e
First 4 bytes: 0xb6 0xbf 0x7b 0x22
As 32-bit value: 0x227bbfb6
Byte analysis:
b0 = 0xb6 (bits: 10110110)
b1 = 0xbf (bits: 10111111)
b2 = 0x7b (bits: 01111011)
b3 = 0x22 (bits: 00100010)
Test 2: Multiple getrandom calls (checking for patterns)
10 samples of 4 bytes each:
Sample 0: 05 c0 84 ee (0xee84c005)
Sample 1: 9b 57 d9 a6 (0xa6d9579b)
Sample 2: 82 84 2a 1c (0x1c2a8482)
Sample 3: 9f 32 13 e9 (0xe913329f)
Sample 4: 98 21 07 2d (0x2d072198)
Sample 5: 46 a5 f9 a7 (0xa7f9a546)
Sample 6: bc bc 23 8e (0x8e23bcbc)
Sample 7: ce e7 92 04 (0x0492e7ce)
Sample 8: 13 da e5 5b (0x5be5da13)
Sample 9: c7 63 5a cd (0xcd5a63c7)
Test 3: Large buffer (1024 bytes)
PASS: Got 1024 bytes
=== Large Buffer Analysis ===
Buffer size: 1024 bytes
Unique byte values: 254 / 256
Byte count range: 1 to 10
First 32 bytes (hex): 25 f8 78 54 bc ab e7 19 23 e0 c5 8c 20 0c 71 21 d7 d7 4e 8a c9 b6 49 c8 57 a8 24 0a 9a 76 c6 23
First 4 bytes: 0x25 0xf8 0x78 0x54
As 32-bit value: 0x5478f825
Byte analysis:
b0 = 0x25 (bits: 00100101)
b1 = 0xf8 (bits: 11111000)
b2 = 0x78 (bits: 01111000)
b3 = 0x54 (bits: 01010100)
Test 4: Zero-length buffer
PASS: Zero-length returns 0
Test 5: NULL pointer
PASS: NULL pointer correctly returns EFAULT
Key Observation: No suspicious bit patterns detected between consecutive bytes.
Actual Behavior (DragonOS)
DragonOS generates random bytes with a bit-shift bug that causes poor distribution and correlation between consecutive bytes.
DragonOS Command:
python3 dragonos_qemu_interactive.py --commands "/bin/test_getrandom_bug"DragonOS Output:
=== Testing getrandom syscall for bit-shift bug ===
Test 1: Basic getrandom call (256 bytes)
PASS: getrandom returned 256 bytes
=== Random Quality Analysis ===
Buffer size: 256 bytes
Unique byte values: 161 / 256
Byte count range: 1 to 4
First 32 bytes (hex): a8 ea ba 6e fa 7e 5f d7 c6 31 cc f3 1e c7 71 5c a4 29 0a 82 ac 6b 9a a6 dc 37 8d 63 04 01 40 10
First 4 bytes: 0xa8 0xea 0xba 0x6e
As 32-bit value: 0x6ebaeaa8
Byte analysis:
b0 = 0xa8 (bits: 10101000)
b1 = 0xea (bits: 11101010)
b2 = 0xba (bits: 10111010)
b3 = 0x6e (bits: 01101110)
Test 2: Multiple getrandom calls (checking for patterns)
10 samples of 4 bytes each:
Sample 0: 6e db f6 fd (0xfdf6db6e)
Sample 1: 14 05 01 c0 (0xc0010514)
Sample 2: a8 ea ba ee (0xeebaeaa8)
Sample 3: d2 b4 6d 1b (0x1b6db4d2)
Sample 4: 8a 62 98 26 (0x2698628a)
Sample 5: d4 35 0d 83 (0x830d35d4)
Sample 6: 8a e2 78 1e (0x1e78e28a)
Sample 7: 44 51 14 05 (0x05145144)
Sample 8: 24 09 82 a0 (0xa0820924)
Sample 9: e4 b9 ee 3b (0x3beeb9e4)
Test 3: Large buffer (1024 bytes)
PASS: Got 1024 bytes
=== Large Buffer Analysis ===
Buffer size: 1024 bytes
Unique byte values: 248 / 256
Byte count range: 1 to 11
First 32 bytes (hex): ba ee fb fe 04 01 40 90 a2 28 ca 32 5a 16 45 51 cc b3 6c 1b 72 1c c7 f1 04 41 90 64 04 81 60 d8
First 4 bytes: 0xba 0xee 0xfb 0xfe
As 32-bit value: 0xfefbeeba
Byte analysis:
b0 = 0xba (bits: 10111010)
b1 = 0xee (bits: 11101110)
b2 = 0xfb (bits: 11111011)
b3 = 0xfe (bits: 11111110)
WARNING: Detected suspicious bit patterns between bytes!
This may indicate the getrandom bit-shift bug (offset * 2 instead of * 8)
Test 4: Zero-length buffer
PASS: Zero-length returns 0
Test 5: NULL pointer
PASS: NULL pointer correctly returns EFAULT
=== Test completed ===
Error Details:
- Return value: Returns correct number of bytes
- errno: Not set (no error returned)
- The bug is subtle - the syscall appears to work correctly but generates poor quality random numbers
- System behavior: Test completes successfully but detects suspicious bit patterns
Analysis
The bug is in kernel/src/syscall/sys_getrandom.rs at line 115:
// 生成随机字节并直接写入用户缓冲区
let mut random_bytes = [0u8; 4];
for (offset, byte) in random_bytes.iter_mut().enumerate().take(step) {
*byte = (rand_value >> (offset * 2)) as u8; // BUG: Should be offset * 8
}The Problem
The code uses offset * 2 instead of offset * 8 for the bit shift. This causes:
- offset 0:
rand_value >> 0(bits 0-7) ✓ correct - offset 1:
rand_value >> 2(bits 2-9) ✗ should be bits 8-15 - offset 2:
rand_value >> 4(bits 4-11) ✗ should be bits 16-23 - offset 3:
rand_value >> 6(bits 6-13) ✗ should be bits 24-31
Impact
- Bit Correlation: Consecutive bytes share overlapping bits, creating predictable patterns
- Poor Distribution: Only 16 bits of the 32-bit random value are used effectively
- Security Risk: The generated random numbers are not cryptographically secure
- Predictability: An attacker could potentially predict future random values
The Fix
Change line 115 from:
*byte = (rand_value >> (offset * 2)) as u8;To:
*byte = (rand_value >> (offset * 8)) as u8;This will correctly extract independent bytes from the 32-bit random value:
- offset 0: bits 0-7
- offset 1: bits 8-15
- offset 2: bits 16-23
- offset 3: bits 24-31
Comparison
| Aspect | Linux | DragonOS (Buggy) |
|---|---|---|
| Return value | Correct | Correct |
| Byte count | Correct | Correct |
| Bit distribution | Random | Correlated |
| Cryptographic quality | Secure | Insecure |
| Pattern detection | None | Suspicious patterns |
Related Files
- DragonOS implementation:
kernel/src/syscall/sys_getrandom.rs:115 - Bug location:
do_get_random()function - Linux reference:
man 2 getrandom
Severity
- Critical - System crash/hang
- High - Wrong behavior, security issue
- Medium - Incorrect return value
- Low - Minor inconsistency
Severity Justification: HIGH severity
-
Security Impact: The
getrandomsyscall is used for generating cryptographic keys, session IDs, IVs for encryption, and other security-sensitive data. Poor random quality can lead to:- Predictable cryptographic keys
- Vulnerable session tokens
- Weak encryption initialization vectors
- Exploitable random number generation in general
-
Cryptography Standards: Any cryptographic implementation relying on
getrandomwill be weakened -
Silent Failure: The syscall doesn't return an error, making this bug difficult to detect without specific testing
-
Wide Impact: Affects all applications using
getrandom()for secure random number generation
Proof of Bug Analysis
Looking at the byte patterns from DragonOS:
First 4 bytes: 0xba 0xee 0xfb 0xfe
b0 = 0xba (bits: 10111010)
b1 = 0xee (bits: 11101110)
b2 = 0xfb (bits: 11111011)
b3 = 0xfe (bits: 11111110)
Notice the pattern:
- All bytes end in
10(bits 1-0 are10) - High bits show correlation:
1011...,1110...,1111...,1111... - The test detected this:
(b0 & 0xC0) == (b1 & 0xC0)- high 2 bits repeating
This is exactly what happens with offset * 2:
- offset 0: shift 0 → bits [7:0]
- offset 1: shift 2 → bits [9:2] (overlaps with offset 0!)
- offset 2: shift 4 → bits [11:4] (more overlap)
- offset 3: shift 6 → bits [13:6] (even more overlap)
The bits are being "smeared" across consecutive bytes instead of being independent!