-
Notifications
You must be signed in to change notification settings - Fork 3k
Open
Description
Describe the issue
While debugging a GRPC crash, I ran into what seems like a potential bug with absl::Cord manual poisoning and the way GRPC uses absl status? See grpc/grpc#40935 for the full crash report.
Steps to reproduce the problem
https://godbolt.org/z/1qz99c4Ka
Reproducer:
#include "absl/status/status.h"
#include "absl/strings/cord.h"
#include <iostream>
// Minimal reproducer for ASAN use-after-poison bug in absl::Status::SetPayload()
//
//
// The bug occurs when:
// 1. Abseil's Cord manually poisons its inline buffer (user poisoning)
// 2. SetPayload() takes Cord by value, creating a copy
// 3. The copy constructor accesses the poisoned inline buffer
//
// To reproduce:
// g++ -std=c++17 -fsanitize=address -I<abseil-include-path> test_abseil_asan_bug.cc -labsl_status -labsl_cord
// ./a.out
//
// Expected with unpatched Abseil:
// AddressSanitizer: use-after-poison on address 0x...
// READ of size 8 at 0x... thread T0
// #0 absl::lts_20250127::status_internal::StatusRep::SetPayload(...)
int main() {
absl::Status status = absl::InternalError("test error");
// Create a temporary Cord with a short string (uses inline buffer)
// Abseil manually poisons this inline buffer
// When SetPayload takes by value, it copies and accesses the poisoned buffer
status.SetPayload("type.googleapis.com/test", absl::Cord("short string"));
std::cout << "Status: " << status << std::endl;
// Check the payload was set
auto payload = status.GetPayload("type.googleapis.com/test");
if (payload) {
std::cout << "Payload: " << *payload << std::endl;
}
return 0;
}Local output:
# bazel run -s --features asan test_abseil_asan_bug
(cd /home/bg/.cache/bazel/_bazel_bgianfo/29dfcfe4ebcb52907732eff370920096/execroot/ && \
exec env - \ PATH=/bin:/sbin:/usr/bin \
PWD=/proc/self/cwd \
clang -U_FORTIFY_SOURCE -fstack-protector -Wall -Wthread-safety -Wself-assign -Wno-deprecated-non-prototype -Wno-free-nonheap-object -fcolor-diagnostics -fno-omit-frame-pointer -O1 '-std=c++17' -MD -MF bazel-out/cfg/bin/_objs/test_abseil_asan_bug/test_abseil_asan_bug.pic.d '-frandom-seed=bazel-out/cfg/bin/_objs/test_abseil_asan_bug/test_abseil_asan_bug.pic.o' -fPIC -iquote . -iquote bazel-out/cfg/bin -iquote external/com_google_absl -iquote bazel-out/cfg/bin/external/com_google_absl -iquote external/bazel_tools -iquote bazel-out/cfg/bin/external/bazel_tools -Og -fno-omit-frame-pointer '-fno-sanitize-recover=all' '-fsanitize=address' -fno-common -g -gdwarf-5 '--sysroot=/x86_64-centos7-linux-gnu/sysroot' '-ffile-prefix-map=/x86_64-centos7-linux-gnu/sysroot=' '-ffile-prefix-map=/tools/clang/21.1.0=' -no-canonical-prefixes -Wno-builtin-macro-redefined '-D__DATE__="redacted"' '-D__TIMESTAMP__="redacted"' '-D__TIME__="redacted"' -Werror -c test_abseil_asan_bug.cc -o bazel-out/cfg/bin/_objs/test_abseil_asan_bug/test_abseil_asan_bug.pic.o)
SUBCOMMAND: # //:test_abseil_asan_bug [action 'Linking test_abseil_asan_bug', configuration:
(cd /home/bg/.cache/bazel/_bazel_bgianfo/29dfcfe4ebcb52907732eff370920096/execroot/ && \
exec env - \
PATH=/bin:/sbin:/usr/bin \
PWD=/proc/self/cwd \
ZERO_AR_DATE=1 \
clang @bazel-out/k8-fastbuild/bin/test_abseil_asan_bug-2.params)
INFO: Found 1 target...
Target //:test_abseil_asan_bug up-to-date:
bazel-bin/test_abseil_asan_bug
INFO: Elapsed time: 12.344s, Critical Path: 12.19s
INFO: 3 processes: 1 internal, 2 remote.
INFO: Build completed successfully, 3 total actions
INFO: Running command line: external/bazel_tools/tools/test/test-setup.sh ./test_abseil_asan_bug
exec ${PAGER:-/usr/bin/less} "$0" || exit 1
Executing tests from //:test_abseil_asan_bug
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
=================================================================
==708862==ERROR: AddressSanitizer: use-after-poison on address 0x6eee6f300060 at pc 0x72ee7264f6aa bp 0x7ffe10e05bf0 sp 0x7ffe10e05be8
READ of size 8 at 0x6eee6f300060 thread T0
#0 0x72ee7264f6a9 in absl::lts_20250127::status_internal::StatusRep::SetPayload(std::__1::basic_string_view<char, std::__1::char_traits<char>>, absl::lts_20250127::Cord) /proc/self/cwd/external/com_google_absl/absl/status/internal/status_internal.cc
#1 0x64411df7fc16 in absl::lts_20250127::Status::SetPayload(std::__1::basic_string_view<char, std::__1::char_traits<char>>, absl::lts_20250127::Cord) /proc/self/cwd/external/com_google_absl/absl/status/status.h:869:8
#2 0x64411df7f6df in main /proc/self/cwd/test_abseil_asan_bug.cc:29:12
#3 0x72ee7142a574 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#4 0x72ee7142a627 in __libc_start_main csu/../csu/libc-start.c:360:3
#5 0x64411de95d88 in _start (/home/bg/.cache/bazel/_bazel_bgianfo/29dfcfe4ebcb52907732eff370920096/execroot//bazel-out/k8-dbg/bin/test_abseil_asan_bug+0xa4d88) (BuildId: 8e2105c2d009d853a7d4379ec2f4fe6f6293ccbe)
Address 0x6eee6f300060 is located in stack of thread T0 at offset 32 in frame
#0 0x64411df7fb1f in absl::lts_20250127::Status::SetPayload(std::__1::basic_string_view<char, std::__1::char_traits<char>>, absl::lts_20250127::Cord) /proc/self/cwd/external/com_google_absl/absl/status/status.h:866
This frame has 1 object(s):
[32, 48) 'agg.tmp' <== Memory access at offset 32 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: use-after-poison /proc/self/cwd/external/com_google_absl/absl/status/internal/status_internal.cc in absl::lts_20250127::status_internal::StatusRep::SetPayload(std::__1::basic_string_view<char, std::__1::char_traits<char>>, absl::lts_20250127::Cord)
Shadow bytes around the buggy address:
0x6eee6f2ffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x6eee6f2ffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x6eee6f2ffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x6eee6f2fff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x6eee6f2fff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x6eee6f300000: f5 f5 f5 f5 f5 f5 f5 f5 f1 f1 f1 f1[f7]f7 f3 f3
0x6eee6f300080: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
0x6eee6f300100: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
0x6eee6f300180: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
0x6eee6f300200: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
0x6eee6f300280: f5 f5 f5 f5 f5 f5 f5 f5 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
NOTE: the stack trace above identifies the code that *accessed* the poisoned memory.
To identify the code that *poisoned* the memory, try the experimental setting ASAN_OPTIONS=poison_history_size=<size>.
==708862==ABORTING
What version of Abseil are you using?
lts_20250127
What operating system and version are you using?
Ubuntu 25.10
# uname -a
Linux 6.17.0-8-generic #8-Ubuntu SMP PREEMPT_DYNAMIC Fri Nov 14 21:44:46 UTC 2025 x86_64 GNU/Linux
What compiler and version are you using?
clang --version
clang version 21.1.0
Target: x86_64-unknown-linux-gnu
Thread model: posix
What build system are you using?
# bazel version
Build label: 7.6.1
Additional context
This patch seems to resolve the reproducing issue, and the original GRPC ASAN issue:
From 0000000000000000000000000000000000000000 Wed Jan 7 00:00:00 2026
From: Brian Gianforcaro <[email protected]>
Date: Wed, 7 Jan 2026 00:00:00 -0000
Subject: [PATCH] Fix ASAN issues with Cord parameter passing
When called with a temporary:
status.SetPayload(key, absl::Cord(str)); // Temporary has poisoned buffer
The temporary Cord's inline buffer is poisoned by ASAN.
When the copy constructor accesses it, ASAN detects use-after-poison.
Abseil's Cord manually poisons its inline buffer (user poisoning) to detect
memory misuse. When absl::Status::SetPayload() accepts Cord by value, it creates
a copy of temporary Cord objects. The copy constructor accesses the manually
poisoned inline buffer, triggering ASAN's use-after-poison detection.
This patch changes the API signature from pass-by-value to pass-by-const-reference:
This eliminates the unnecessary copy operation that was accessing the manually-poisoned memory.
---
absl/status/internal/status_internal.cc | 2 +-
absl/status/internal/status_internal.h | 2 +-
absl/status/status.h | 6 +++---
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/absl/status/status.h b/absl/status/status.h
--- a/absl/status/status.h
+++ b/absl/status/status.h
@@ -586,7 +586,7 @@ class Status final {
// any existing payload for that `type_url`.
//
// NOTE: This function does nothing if the Status is ok.
- void SetPayload(absl::string_view type_url, absl::Cord payload);
+ void SetPayload(absl::string_view type_url, const absl::Cord& payload);
// Status::ErasePayload()
//
@@ -863,7 +863,7 @@ inline absl::optional<absl::Cord> Status::GetPayload(
return RepToPointer(rep_)->GetPayload(type_url);
}
-inline void Status::SetPayload(absl::string_view type_url, absl::Cord payload) {
+inline void Status::SetPayload(absl::string_view type_url, const absl::Cord& payload) {
if (ok()) return;
status_internal::StatusRep* rep = PrepareToModify(rep_);
rep->SetPayload(type_url, std::move(payload));
diff --git a/absl/status/internal/status_internal.h b/absl/status/internal/status_internal.h
--- a/absl/status/internal/status_internal.h
+++ b/absl/status/internal/status_internal.h
@@ -82,7 +82,7 @@ class StatusRep {
// Payload methods correspond to the same methods in absl::Status.
absl::optional<absl::Cord> GetPayload(absl::string_view type_url) const;
- void SetPayload(absl::string_view type_url, absl::Cord payload);
+ void SetPayload(absl::string_view type_url, const absl::Cord& payload);
struct EraseResult {
bool erased;
uintptr_t new_rep;
diff --git a/absl/status/internal/status_internal.cc b/absl/status/internal/status_internal.cc
--- a/absl/status/internal/status_internal.cc
+++ b/absl/status/internal/status_internal.cc
@@ -76,7 +76,7 @@ absl::optional<absl::Cord> StatusRep::GetPayload(
return absl::nullopt;
}
-void StatusRep::SetPayload(absl::string_view type_url, absl::Cord payload) {
+void StatusRep::SetPayload(absl::string_view type_url, const absl::Cord& payload) {
if (payloads_ == nullptr) {
payloads_ = absl::make_unique<status_internal::Payloads>();
}
--
2.49.0Metadata
Metadata
Assignees
Labels
No labels