Skip to content

ci(rdc): run the no-GPU tests under ASan and under UBSan - #14049

Merged
joshnguy-amd merged 5 commits into
developfrom
users/adam360x/rdc-ci-sanitizers
Nov 6, 2026
Merged

joshnguy-amd merged 5 commits into
developfrom
users/adam360x/rdc-ci-sanitizers

Conversation

@adam360x

@adam360x adam360x commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #14041 as a draft: it shows only its own commit. When #14041 merges, this PR moves to develop, where RDC CI runs, and becomes ready for review.

Motivation

RDC's tests run without sanitizers, so a read past a buffer or an invalid enum value that does not crash goes unseen.

Technical Details

  • A new job, rdc_sanitize, has two legs: GCC -fsanitize=address and -fsanitize=undefined, one build each. With both runtimes in one process, UBSan's reports go to stderr instead of log_path.
  • Each leg runs the installed gtests that need no GPU (all but rdc_diag_kernel_tests, which has no install RPATH), the fake-GPU suite (test(rdc): run rdcd and rdci in CI against a fake amd-smi #14041) and tests/ci against its build. Every process writes its reports to one directory.
  • A later step prints them, lists the distinct errors in the job summary, and fails the leg if there are any, if a report lands in output the tests keep (such as rdcd's logs) instead, or if no test ran.
  • Before the tests, a step checks that the binaries load the leg's runtime, that librdc calls its checks, and that a program with a known bug leaves a report at log_path.
  • ASan's leak detection is off (Python under the runtime does not free everything at exit, and rdcd and rdci inherit its options), and so is verify_asan_link_order (the tests preload the fake amd-smi, which links the runtime, into rdcd).
  • The legs build gRPC 1.67.1 as rdc_build does and cache it. They do not gate until the repo variable RDC_SANITIZE_GATING is true.

CI-only; no CHANGELOG entry.

Issue Tracking

ISSUE ID : #14107

Part of a series of RDC fixes, which #14107 lists. No JIRA ticket.

Test Plan

Ran each leg's build and test steps on this branch in a GPU-less Ubuntu 22.04 container.

Test Result

Both legs pass every test: the 8 gtest programs the job runs (43 cases), 106 fake-GPU tests (24 skipped), and tests/ci (92 passed, 6 skipped; test_prometheus_help needs prometheus_client, which the job installs).

The ASan leg reports nothing. The UBSan leg reports, in 89 processes, findings that are on develop today:

  • a reference bound to a null pointer when RdcTelemetryModule passes &v[0] of an empty vector (the telemetry fix in this series, fix(rdc): bound the fields and devices rdcd handles at once #14045, removes it),
  • an invalid rdc_field_type_t in fetch_smi_field() and the cache, because RDC_HEALTH_EEPROM_CONFIG_VALID reports success without setting a type or value,
  • the link type and P2P flag that rdc_device_topology_get() reads after a failed amd-smi call,
  • an invalid rdc_field_t where rdci dmon, the client library and rdcd load the field ID 99999 that a tests/ci test passes on purpose.

Submission Checklist

A new job, rdc_sanitize, has two legs. One builds RDC with GCC's
-fsanitize=address and the other with -fsanitize=undefined, and each
runs the installed gtests that need no GPU (all but
rdc_diag_kernel_tests, which has no install RPATH), the fake-GPU suite
and tests/ci against its build. GCC links the sanitizer runtime as a
shared library, so rdcd, rdci, the gtests, the fake amd-smi and Python,
which loads RDC's libraries through ctypes and gets the runtime through
LD_PRELOAD, share one runtime. Every process writes its reports to one
directory. A later step prints them, lists the distinct errors in the
job summary and fails the job if there are any, if a report shows up
instead in output the tests keep (such as rdcd's logs), or if the
directory is missing because no test ran. The legs report what the
other jobs cannot see, such as a read past a stack array or an invalid
enum value that does not crash.

The sanitizers get separate builds because in a process with both of
GCC's runtimes, UBSan's reports go to stderr instead of to log_path
(both runtimes export __sanitizer_set_report_path, and ASan's copy
wins). The tests capture that stderr, so with one combined build the
log directory had one report while rdcd's captured output held 270
UBSan errors.

Before the tests, a step checks that rdcd, rdci, librdc,
librdc_bootstrap and the fake amd-smi load the leg's runtime, that
librdc calls the leg's checks, and that a program with a known bug
leaves a report at log_path, so a build without the sanitizer fails
instead of passing quietly. ASan's leak detection is off: Python under
the runtime does not free everything at exit (151 allocations for
"import ctypes, json"), and rdcd and rdci inherit its options.
verify_asan_link_order is off because the tests preload the fake
amd-smi, which links the runtime, ahead of it into rdcd; with the check
on, rdcd exits at once.

The legs build the same gRPC 1.67.1 as rdc_build and cache the install;
each leg's ccache has its own key. Both are restored and saved in
separate steps, since actions/cache saves nothing when a job fails. The
legs name gcc and g++, build amd-smi with E-SMI and without a
sanitizer, and build RDC without the runtime, profiler and RVS modules,
which need ROCm's GPU runtime; rdcd runs without them. Each gtest has a
time limit and writes a JUnit report, and the JUnit results go to the
job summary as in the other test jobs. The legs do not gate until the
repo variable RDC_SANITIZE_GATING is 'true'.

On develop with the fake-GPU suite, in a GPU-less Ubuntu 22.04
container rather than the job's image, both legs' test runs pass every
test: the 8 gtest programs the job runs (43 cases), 106 fake-GPU tests
(24 skipped), and tests/ci but for test_prometheus_help, which needs
prometheus_client (the job installs it). The ASan leg reports nothing.
The UBSan leg reports, in 89 processes: a reference bound to a null
pointer when RdcTelemetryModule passes &v[0] of an empty vector; an
invalid rdc_field_type_t in fetch_smi_field() and the cache, because
RDC_HEALTH_EEPROM_CONFIG_VALID reports success without setting a type
or value, which then come from RdcSmiLib's uninitialized values array;
the link type and P2P flag that rdc_device_topology_get() reads after a
failed amd-smi call; and an invalid rdc_field_t where rdci dmon, the
client library and rdcd load the field ID 99999 that a tests/ci test
passes on purpose.

GCC rather than clang for now: in local builds, ROCm's clang 22 with
-shared-libsan reported ODR violations in the gtests (each string
literal registered twice), and with clang's static runtime every
process that loads RDC's instrumented libraries without being an
instrumented executable (Python, setpriv, llvm-symbolizer) needed the
runtime preloaded. A clang build like TheRock's is a follow-up.

Signed-off-by: Adam360x <Adam.pryor@amd.com>
@joshnguy-amd
joshnguy-amd force-pushed the users/adam360x/rdc-ci-sanitizers branch from db0b967 to ec58d80 Compare November 4, 2026 21:54
Base automatically changed from users/adam360x/rdc-fake-gpu-tests to develop November 5, 2026 16:51
@therock-pr-bot

therock-pr-bot Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

✅ All Policy Checks Passed

Check Status Details
📝 PR Description ✅ Pass —
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ⚠️ Warning Error: Source/code files changed without an accompanying unit test.
Expected: add at least one test file named like test_<name>.py / test_<name>.cpp (or <name>_test.*).
Current: code file(s) changed: projects/rdc/common/rdc_fields_supported.cc, projects/rdc/common/rdc_fields_supported.h, projects/rdc/include/rdc_lib/rdc_common.h, projects/rdc/rdc_libs/rdc/src/RdcEmbeddedHandler.cc, projects/rdc/rdc_libs/rdc/src/RdcMetricFetcherImpl.cc (+7 more); no test file found
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —

🎉 All policy checks passed!

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

🙋 Wish to Override Policy?

The UBSan leg of rdc_sanitize reported 13 distinct findings in 89 processes.
Nine of them come from three bugs:

- RdcTelemetryModule passed &unsupport_fields[0] to the callback. The vector
  is usually empty, and indexing an empty vector binds a reference to null
  (87 processes). Use data(), which is defined for an empty vector.
- fetch_smi_field set a field's status but never its type, so a successful
  integer field reached the cache and the log with an arbitrary
  rdc_field_type_t when the caller's value was uninitialised. Default the type
  to INTEGER; the string fields already set their own.
- RdcTopologyLinkImpl used the output parameters of a failed amd-smi call. A
  failed call may still write them: the fake amd-smi writes a poison pattern,
  which UBSan reports as an invalid amdsmi_link_type_t and bool. Reset each
  output to N/A on failure, which is what the code's own comment says a failed
  lookup reports.

After this the leg reports 4 findings in 2 processes, all rdc_field_t values
that are not valid field ids. Those are covered by #14102 and #14160.
adam360x and others added 2 commits November 5, 2026 12:53
The source changes of #14102 (22d79f4), without its tests, so that the UBSan
leg added by this PR runs clean.

A caller can pass any 32-bit value as an rdc_field_t, and loading a value the
enum does not define is undefined behavior. UBSan reports it for field ids such
as 99999 in rdci dmon, rdci fieldgroup and rdcd's CreateFieldGroup request. Read
the number from the bytes first (rdc_field_id_number), check it against the
field table (is_field_id_valid, get_field_id_from_number), and convert to
rdc_field_t only when it is a field RDC defines. rdcd now compiles
rdc_fields_supported.cc to do the same check.

With this the UBSan leg reports no findings: it was 13 before the earlier fix
commit on this branch and 4 after it.
rdc_sanitize (address) failed on lsttstrix in
test_rdcd_survives_a_failing_call[amdsmi_get_gpu_event_notification-fail]:
rdcd printed "RDC server successfully shut down.", then AddressSanitizer
reported a SEGV in the dynamic loader, and the exit code was 1.

On SIGTERM the signal thread calls ShutDown(), which shuts the gRPC server
down and then deletes the services, destroying the handler and waiting for the
metrics updater's threads. Shutting the gRPC server down makes Run() return in
main, which printed the message and returned at once, so the process started
to exit while the signal thread was still deleting. The thread was detached,
so nothing waited for it.

Do not detach the signal thread; when main returns after a shutdown signal,
join it first. The thread ends after ShutDown(), so the join returns. If the
server fails to start, the thread is still looping and main does not join it,
as before.

The race did not reproduce locally: the failing test passed 40 of 40 runs with
every CPU busy, and both sanitizer legs pass with and without this change. The
cause comes from the code and from the order of the messages in the failing
log. This overlaps #14048, which reorders the same shutdown more broadly.
@adam360x
adam360x marked this pull request as ready for review November 6, 2026 14:26
@adam360x
adam360x requested a review from a team as a code owner November 6, 2026 14:26
Copilot AI balanced review requested due to automatic review settings November 6, 2026 14:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are recorded for the sanitizer CI additions and accompanying runtime fixes.

No open issues 🎉

What changed in this PR

Adds separate AddressSanitizer and UndefinedBehaviorSanitizer CI jobs for RDC’s GPU-less tests, building on the fake amd-smi suite. The diff also includes runtime fixes for shutdown, field-ID validation, and telemetry handling.

Changes:

  • Build and run GPU-less tests under each sanitizer, verify instrumentation, and publish reports with optional gating.
  • Validate numeric field IDs before enum conversion and preserve raw IDs across client requests.
  • Join the shutdown thread, reset failed topology outputs, initialize metric types, and avoid indexing an empty telemetry vector.
File Description
projects/​rdc/​server/​src/​rdc_server_main.cc Join the signal thread before successful shutdown exit.
projects/​rdc/​server/​src/​rdc_api_service.cc Validate incoming field IDs before enum conversion.
projects/​rdc/​server/​CMakeLists.txt Link field-validation helpers into the server.
projects/​rdc/​rdci/​src/​RdciFieldGroupSubSystem.cc Validate numeric field IDs for field-group operations.
projects/​rdc/​rdci/​src/​RdciDmonSubSystem.cc Validate numeric monitoring field IDs.
projects/​rdc/​rdc_libs/​rdc/​src/​RdcTopologyLinkImpl.cc Reset topology outputs after failed amd-smi calls.
projects/​rdc/​rdc_libs/​rdc/​src/​RdcTelemetryModule.cc Use data() instead of indexing a potentially empty vector.
projects/​rdc/​rdc_libs/​rdc/​src/​RdcMetricFetcherImpl.cc Initialize the metric value type to integer.
projects/​rdc/​rdc_libs/​rdc/​src/​RdcEmbeddedHandler.cc Validate caller-provided field IDs as numbers.
projects/​rdc/​rdc_libs/​rdc_client/​src/​RdcStandaloneHandler.cc Serialize raw numeric field IDs for server validation.
projects/​rdc/​include/​rdc_lib/​rdc_common.h Add byte-copy extraction of numeric field IDs.
projects/​rdc/​common/​rdc_fields_supported.h Declare numeric field-ID validation and parsing helpers.
projects/​rdc/​common/​rdc_fields_supported.cc Implement numeric field-ID validation and parsing.
.github/​workflows/​rdc-ci.yml Add sanitizer builds, runtime checks, test execution, and reporting.

Review effort: Balanced

@joshnguy-amd joshnguy-amd added the ci:skip Skip all CI builds/tests for this PR label Nov 6, 2026
@joshnguy-amd
joshnguy-amd merged commit c8af82f into develop Nov 6, 2026
40 of 45 checks passed
@joshnguy-amd
joshnguy-amd deleted the users/adam360x/rdc-ci-sanitizers branch November 6, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:skip Skip all CI builds/tests for this PR github actions Pull requests that update GitHub Actions code organization: ROCm project: rdc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants