Files
doris/cloud/test/fdb_getentropy_compat.c
Gavin Chou ad7e343bf2 [fix](cloud) Prevent FDB CloudUT ASAN getentropy crash (#67316)
### What problem does this PR solve?

Issue Number: None

Related PR: #65749

Problem Summary:

Several FDB-related CloudUT ASAN binaries can terminate with a zero-page
SEGV in `libfdb_c.so`. The existing runner fallback from #65749 prevents
the overall job from failing when the log contains both ASAN output and
`libfdb_c.so`, but it does not fix the binary or guarantee that its
GoogleTest cases actually ran. This PR fixes the underlying
symbol-resolution failure.

### Root cause

1. CloudUT ASAN executables are compiled against glibc 2.27, where
`getentropy` exists, and run on workers with glibc 2.17, where it does
not.
2. Compiler-rt links a `getentropy` ASAN interceptor into each
executable. `libfdb_c` checks `getentropy` as an optional weak symbol
during FDB network initialization, so the interceptor makes the
availability check succeed.
3. The interceptor tries to forward the call to libc, but
`dlsym(RTLD_NEXT, "getentropy")` resolves to null on glibc 2.17.
4. The call therefore jumps to address zero, producing
`AddressSanitizer:DEADLYSIGNAL`, `pc=0`, and a non-zero test-binary
result.

An A/B diagnostic CloudUT run ([build
52882](http://47.243.177.214:8111/buildConfiguration/SelectdbCore_Cloudut/52882))
observed the full chain:

- compile-time glibc 2.27 and runtime glibc 2.17;
- native `getentropy` resolved to `__interceptor_trampoline_getentropy`;
- `RTLD_NEXT getentropy` resolved to null;
- the native probe reproduced the zero-page SEGV;
- a probe exporting a strong syscall-backed `getentropy` was called
during FDB network startup and completed successfully.

Changing `fdb_external_client_directory` and aligning FDB client/server
versions were also tested independently; neither changed the failure.
This ruled out FDB client selection and protocol-version compatibility.

### Why this fix works

- A strong `getentropy` implementation is linked directly into Linux
ASAN CloudUT executables that link or initialize FDB.
- `ENABLE_EXPORTS` puts the executable symbol in the dynamic symbol
table, so `libfdb_c.so` resolves `getentropy` to this implementation
instead of the unusable ASAN trampoline.
- The implementation calls `SYS_getrandom` directly, avoiding both libc
`getentropy` and libc `getrandom` interceptor paths.
- It preserves the `getentropy(3)` 256-byte limit, retries interrupted
operations, and falls back to `/dev/urandom` when the running kernel
reports `ENOSYS`.
- The compatibility object is limited to Linux ASAN unit tests.
Production binaries, non-ASAN builds, FDB versions, and FDB
configuration are unchanged.

The BE glibc-compatibility objects cannot be reused directly here
because Cloud is a separate CMake project and does not link them. That
implementation also delegates to `getrandom`, while this workaround
deliberately uses a direct syscall to avoid another interceptor
dependency.

### What is changed?

- Add a Linux ASAN test-only, syscall-backed `getentropy`
implementation.
- Export it from the FDB-linked/FDB-initializing CloudUT executables.
- Keep the existing FDB client version, external-client configuration,
and test runner unchanged.

### Testing

- A/B CloudUT build 52882: native probe reproduced the zero-page crash;
the strong-symbol probe passed and recorded the FDB network call.
- CloudUT [build
53003](http://47.243.177.214:8111/buildConfiguration/SelectdbCore_Cloudut/53003):
all four previously crashing binaries returned raw `ret=0`; their 1 + 19
+ 22 + 24 FDB tests (66 total) passed without invoking ASAN-result
masking.
- Apache Doris master CloudUT on this PR: [build
1034381](http://43.132.222.7:8111/viewLog.html?buildId=1034381&buildTypeId=Doris_DorisCloudUt_CloudUt),
running.
- Local checks:
    - clang-format 16 dry-run;
- C11 `-Wall -Wextra -Werror` syntax checks for both the fallback and
`SYS_getrandom` paths;
    - CMake target-existence check;
    - `git diff --check`.

### Release note

None

### Check List (For Author)

- Test
    - [ ] Regression test
    - [x] Unit Test
    - [ ] Manual test (add detailed scripts or steps below)
    - [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
        - [ ] Previous test can cover this change.
        - [ ] No code files have been changed.
        - [ ] Other reason

- Behavior changed:
    - [x] No. Production binaries and FDB configuration are unchanged.
    - [ ] Yes.

- Does this need documentation?
    - [x] No.
    - [ ] Yes.

### Check List (For Reviewer who merge this PR)

- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
2026-08-31 20:27:18 +08:00

114 lines
3.6 KiB
C

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !defined(SYS_getrandom) && defined(__NR_getrandom)
#define SYS_getrandom __NR_getrandom
#endif
// Why this compatibility symbol is needed:
//
// CloudUT ASAN executables are built against glibc 2.27 and run against glibc
// 2.17. The ASAN runtime exports a getentropy interceptor, so libfdb_c's weak
// symbol check concludes that getentropy is available. At runtime, however,
// the interceptor cannot resolve a next libc implementation (RTLD_NEXT is
// null on glibc 2.17) and calling it jumps to address zero.
//
// This file is linked only into Linux ASAN test executables that initialize
// the FDB network. Its strong, exported symbol gives libfdb_c a valid target
// in those processes. Calling the kernel directly is important: forwarding to
// a libc entropy function could enter the same interceptor path again.
// Build headers may define SYS_getrandom even when an older worker kernel does
// not implement it. Fall back to /dev/urandom only when the syscall reports
// ENOSYS.
static int fill_from_urandom(unsigned char* output, size_t length) {
int fd;
do {
fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
} while (fd < 0 && errno == EINTR);
if (fd < 0) {
return -1;
}
while (length > 0) {
const ssize_t bytes_read = read(fd, output, length);
if (bytes_read > 0) {
output += bytes_read;
length -= (size_t)bytes_read;
continue;
}
if (bytes_read < 0 && errno == EINTR) {
continue;
}
const int saved_errno = bytes_read == 0 ? EIO : errno;
close(fd);
errno = saved_errno;
return -1;
}
return close(fd);
}
int getentropy(void* buffer, size_t length) {
// Match the getentropy(3) contract. FDB requests small buffers, but keeping
// the standard 256-byte limit makes this a safe process-wide replacement.
if (length > 256) {
errno = EIO;
return -1;
}
unsigned char* output = (unsigned char*)buffer;
#if defined(SYS_getrandom)
// Use syscall rather than libc getrandom/getentropy so this implementation
// cannot recurse through an ASAN interceptor.
while (length > 0) {
const long bytes_read = syscall(SYS_getrandom, output, length, 0);
if (bytes_read > 0) {
output += bytes_read;
length -= (size_t)bytes_read;
continue;
}
if (bytes_read < 0 && errno == EINTR) {
continue;
}
if (bytes_read < 0 && errno == ENOSYS) {
break;
}
if (bytes_read == 0) {
errno = EIO;
}
return -1;
}
if (length == 0) {
return 0;
}
#endif
return fill_from_urandom(output, length);
}