Skip to content

aws_credentials: fix use-after-free when a refresh races get_credentials() - #12207

Open
Nahid-NHB wants to merge 2 commits into
fluent:masterfrom
Nahid-NHB:fix-12206-aws-creds-uaf
Open

aws_credentials: fix use-after-free when a refresh races get_credentials()#12207
Nahid-NHB wants to merge 2 commits into
fluent:masterfrom
Nahid-NHB:fix-12206-aws-creds-uaf

Conversation

@Nahid-NHB

@Nahid-NHB Nahid-NHB commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Every AWS credential provider keeps one cached flb_aws_credentials and hands out copies of it. get_credentials() made that copy without holding the provider lock, while the refresh path freed and replaced the cached struct under it. That was safe back when flushes were coroutines on one thread, but with workers N they run on N real pthreads, so the copy and the swap overlap.

Three ways it goes wrong:

  • a reader dereferences a pointer the refresh already freed and takes a SIGSEGV in flb_sds_create()
  • a reader lands between *creds = NULL and the reassignment and comes back with nothing
  • a reader picks up the access key from one refresh and the secret from the next, which shows up as InvalidSignatureException with no crash

Same shape in all five providers: sts, eks, ec2, http, profile. So ECS, EC2 instance role and profile deployments are exposed too, not just EKS/IRSA.

Readers can't just take provider->lock. It's held across the network call that fetches new credentials, and that call yields the coroutine, so a second coroutine on the same thread blocking on it would deadlock. That's why the code uses trylock in the first place.

So this adds a second mutex, provider->cache_lock, that is only ever held for the copy and for the pointer swap and never across IO, which makes a blocking lock safe. Both sides now go through helpers:

  • flb_aws_cache_get_credentials() copies all three fields under the lock
  • flb_aws_cache_set_credentials() publishes the new credentials first and frees the old ones after releasing the lock, so the cache is never empty mid-swap
  • flb_aws_cache_get_refresh_time() reads next_refresh, which is part of the same cached state

Lock order is always lock then cache_lock, never the other way round.

The copy block was duplicated in all five providers, so collapsing it into one helper removes more code than it adds.

Fixes #12206


Enter [N/A] in the box, if an item is not applicable to your change.

Testing
Before we can approve your change; please submit the following in a comment:

  • Example configuration file for the change
  • Debug log output from testing the change
  • Attached Valgrind output that shows no leaks or memory corruption was found

Valgrind is installed here but refuses to start without glibc debuginfo, which I can't install on this box, so I used ASan with LeakSanitizer instead. For a use-after-free across threads that's the better tool anyway, since memcheck serialises threads and would likely never hit the window. Full ASan output is in a comment below: it reproduces the exact stack from the issue against the unpatched code, and all six AWS credential test binaries come back clean with the patch.

I also added eks_credential_provider_concurrent_refresh to tests/internal/aws_credentials_sts.c. Six reader threads call get_credentials() in a loop while a seventh refreshes the provider 3000 times. The mock STS response tags the access key, secret key and session token with the same generation number, so a set that was stitched together from two refreshes is detectable without a sanitizer.

Against the unpatched providers it segfaults within a second, which is the crash from the issue:

$ git stash push src include        # keep the test, drop the fix
$ cmake --build build --target flb-it-aws_credentials_sts
$ ./build/bin/flb-it-aws_credentials_sts eks_credential_provider_concurrent_refresh
Test eks_credential_provider_concurrent_refresh...
rc=139

With the fix in place it passes, and so does everything else:

aws_credentials              PASS
aws_credentials_ec2          PASS
aws_credentials_http         PASS
aws_credentials_profile      PASS
aws_credentials_process      PASS
aws_credentials_sts          PASS
aws_util                     PASS

Ran the concurrency test 10 more times to check it isn't flaky, no failures. Under ASan or TSan it also flags the use-after-free directly rather than just the torn read.

The reproducer config from the issue (tail -> cloudwatch_logs with workers 25 on EKS with IRSA) is the real world case this covers.

If this is a change to packaging of containers or native binaries then please confirm it works for all targets.

  • [N/A] Run local packaging test showing all targets (including any new ones) build.
  • [N/A] Set ok-package-test label to test for all targets (requires maintainer to do).

Documentation

  • [N/A] Documentation required for this feature

No user visible behaviour change, nothing to document.

Backporting

  • Backport to latest stable release.

The code is identical from 1.8 through current master, so every supported release is affected.


Fluent Bit is licensed under Apache 2.0, by submitting this pull request I understand that this code will be released under the terms of that license.

Summary by CodeRabbit

  • Bug Fixes

    • Improved AWS credential handling during concurrent access and refresh operations.
    • Prevented inconsistent or incomplete credentials from being returned while credentials are refreshed.
    • Applied safer caching behavior across EC2, HTTP, profile, STS, and EKS credential providers.
  • Tests

    • Added concurrency coverage to verify stable credentials during repeated refreshes and simultaneous requests.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

AWS credential providers now use a dedicated cache mutex. New helpers synchronize credential copies, refresh timestamps, and cache replacement. EC2, HTTP, profile, STS, and EKS providers use these helpers. A multithreaded EKS regression test covers concurrent refreshes.

AWS credential cache synchronization

Layer / File(s) Summary
Cache contract and synchronization primitives
include/fluent-bit/flb_aws_credentials.h, src/aws/flb_aws_credentials.c
Adds cache_lock and thread-safe helpers for reading, copying, and replacing cached credentials and refresh timestamps.
EC2, HTTP, and profile cache integration
src/aws/flb_aws_credentials_ec2.c, src/aws/flb_aws_credentials_http.c, src/aws/flb_aws_credentials_profile.c
Updates provider retrieval, refresh, initialization, and publication paths to use the shared cache APIs.
STS and EKS cache integration
src/aws/flb_aws_credentials_sts.c
Updates STS and EKS retrieval and request paths to use synchronized cache reads and publication.
Concurrent refresh regression coverage
tests/internal/aws_credentials_sts.c
Adds generation-tagged mock responses and a multithreaded EKS test for null or inconsistent credential copies.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: cosmo0920, edsiper

Sequence Diagram(s)

sequenceDiagram
  participant Worker
  participant AWSProvider
  participant CredentialCache
  participant STS
  Worker->>AWSProvider: request credentials
  AWSProvider->>CredentialCache: read refresh time and copy credentials
  alt credentials require refresh
    AWSProvider->>STS: request refreshed credentials
    STS-->>AWSProvider: return credentials and expiration
    AWSProvider->>CredentialCache: atomically publish credentials and deadline
  end
  CredentialCache-->>AWSProvider: return consistent credential copy
  AWSProvider-->>Worker: return credentials
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: fixing a use-after-free race during AWS credential refresh and retrieval.
Linked Issues check ✅ Passed The changes address issue #12206 by synchronizing credential cache reads and replacements across STS, EKS, EC2, HTTP, and profile providers.
Out of Scope Changes check ✅ Passed The implementation and concurrency regression test remain within the linked issue scope of preventing AWS credential cache races.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a9fdeb45e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tests/internal/aws_credentials_sts.c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/aws/flb_aws_credentials_profile.c (1)

127-131: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider returning the still-valid copy when the trylock fails.

If another thread holds the provider lock, this path discards creds and returns NULL, even when the copy is still usable. The EC2, HTTP, and STS providers return the stale copy in the same situation. Returning the copy here avoids an unnecessary signing failure while a refresh is in progress. The current code is not a regression, so treat this as optional.

♻️ Proposed change
         } else {
             AWS_CREDS_WARN("Another thread is refreshing credentials, will retry");
-            flb_aws_credentials_destroy(creds);
-            return NULL;
+            if (!creds) {
+                return NULL;
+            }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aws/flb_aws_credentials_profile.c` around lines 127 - 131, Optionally
update the trylock-failure branch in flb_aws_credentials_refresh to return the
still-valid creds copy instead of destroying it and returning NULL, matching the
EC2, HTTP, and STS provider behavior while another thread refreshes credentials.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aws/flb_aws_credentials_profile.c`:
- Around line 719-723: Update the expiration adjustment in
get_credentials_fn_profile so subtracting FLB_AWS_REFRESH_WINDOW never produces
zero or a negative deadline; clamp positive expiration values at or below the
refresh window to a positive value that causes the next call to refresh, while
preserving the existing zero-expiration handling.

In `@src/aws/flb_aws_credentials.c`:
- Around line 543-544: Update flb_aws_env_provider_create() to initialize
provider->cache_lock with pthread_mutex_init alongside provider->lock, ensuring
flb_aws_provider_destroy() always destroys an initialized mutex; alternatively,
track creation state and guard cache_lock destruction in the destroy path.

In `@tests/internal/aws_credentials_sts.c`:
- Around line 675-745: Make concurrency_ctx.stop an atomic flag and use atomic
loads in concurrency_reader and an atomic store in concurrency_writer,
preserving the existing loop termination behavior. Update the test setup
initialization alongside the concurrency_ctx initialization to initialize the
atomic value correctly, eliminating the writer/readers data race.
- Around line 814-826: Track successful thread creation separately in the
concurrency test around pthread_create calls for concurrency_reader and
concurrency_writer, and only call pthread_join for threads whose creation
returned success. Ensure failed creations do not leave an uninitialized
pthread_t passed to pthread_join while preserving the existing continuation
behavior after TEST_CHECK.

---

Nitpick comments:
In `@src/aws/flb_aws_credentials_profile.c`:
- Around line 127-131: Optionally update the trylock-failure branch in
flb_aws_credentials_refresh to return the still-valid creds copy instead of
destroying it and returning NULL, matching the EC2, HTTP, and STS provider
behavior while another thread refreshes credentials.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e49ee57-906e-4a1f-914b-6333b6d643f1

📥 Commits

Reviewing files that changed from the base of the PR and between 819111c and 0a9fdeb.

📒 Files selected for processing (7)
  • include/fluent-bit/flb_aws_credentials.h
  • src/aws/flb_aws_credentials.c
  • src/aws/flb_aws_credentials_ec2.c
  • src/aws/flb_aws_credentials_http.c
  • src/aws/flb_aws_credentials_profile.c
  • src/aws/flb_aws_credentials_sts.c
  • tests/internal/aws_credentials_sts.c

Comment thread src/aws/flb_aws_credentials_profile.c
Comment thread src/aws/flb_aws_credentials.c
Comment on lines +675 to +745
struct concurrency_ctx {
struct flb_aws_provider *provider;
int stop;

/* results, one slot per reader so they need no synchronization */
int null_creds[CONCURRENCY_READERS];
int torn_creds[CONCURRENCY_READERS];
int reads[CONCURRENCY_READERS];
};

struct concurrency_reader_arg {
struct concurrency_ctx *ctx;
int id;
};

/* Returns the generation number in a "<prefix>_<n>" credential field */
static int credential_generation(const char *value, const char *prefix)
{
size_t prefix_len = strlen(prefix);

if (!value || strncmp(value, prefix, prefix_len) != 0) {
return -1;
}

return atoi(value + prefix_len);
}

static void *concurrency_reader(void *arg)
{
struct concurrency_reader_arg *reader = arg;
struct concurrency_ctx *ctx = reader->ctx;
struct flb_aws_provider *provider = ctx->provider;
struct flb_aws_credentials *creds;
int akid;
int skid;
int token;

while (ctx->stop == FLB_FALSE) {
creds = provider->provider_vtable->get_credentials(provider);
if (!creds) {
ctx->null_creds[reader->id]++;
continue;
}

akid = credential_generation(creds->access_key_id, "akid_");
skid = credential_generation(creds->secret_access_key, "skid_");
token = credential_generation(creds->session_token, "token_");

if (akid < 0 || akid != skid || akid != token) {
ctx->torn_creds[reader->id]++;
}

ctx->reads[reader->id]++;
flb_aws_credentials_destroy(creds);
}

return NULL;
}

static void *concurrency_writer(void *arg)
{
struct concurrency_ctx *ctx = arg;
struct flb_aws_provider *provider = ctx->provider;
int i;

for (i = 0; i < CONCURRENCY_REFRESHES; i++) {
provider->provider_vtable->refresh(provider);
}

ctx->stop = FLB_TRUE;
return NULL;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Synchronize the stop flag between the writer and the readers.

ctx.stop is a plain int. The writer stores it at Line 744. The readers load it at Line 712 with no synchronization. Two problems follow:

  1. The compiler can hoist the load out of the reader loop, because nothing in the loop body is visibly related to ctx.stop. The readers then spin forever and pthread_join at Line 826 hangs the test suite.
  2. The comment at Lines 668-669 asks the maintainer to run this test under TSan. TSan reports this store/load pair as a data race, which adds noise to the exact signal the test is meant to produce.

Use a C11 atomic for the flag, or guard it with a mutex.

🔒 Proposed fix using C11 atomics
+#include <stdatomic.h>
+
 struct concurrency_ctx {
     struct flb_aws_provider *provider;
-    int stop;
+    atomic_int stop;
 
     /* results, one slot per reader so they need no synchronization */
     int null_creds[CONCURRENCY_READERS];
     int torn_creds[CONCURRENCY_READERS];
     int reads[CONCURRENCY_READERS];
 };
-    while (ctx->stop == FLB_FALSE) {
+    while (atomic_load(&ctx->stop) == FLB_FALSE) {
-    ctx->stop = FLB_TRUE;
+    atomic_store(&ctx->stop, FLB_TRUE);

Also update the initialization at Line 812:

-    ctx.stop = FLB_FALSE;
+    atomic_store(&ctx.stop, FLB_FALSE);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
struct concurrency_ctx {
struct flb_aws_provider *provider;
int stop;
/* results, one slot per reader so they need no synchronization */
int null_creds[CONCURRENCY_READERS];
int torn_creds[CONCURRENCY_READERS];
int reads[CONCURRENCY_READERS];
};
struct concurrency_reader_arg {
struct concurrency_ctx *ctx;
int id;
};
/* Returns the generation number in a "<prefix>_<n>" credential field */
static int credential_generation(const char *value, const char *prefix)
{
size_t prefix_len = strlen(prefix);
if (!value || strncmp(value, prefix, prefix_len) != 0) {
return -1;
}
return atoi(value + prefix_len);
}
static void *concurrency_reader(void *arg)
{
struct concurrency_reader_arg *reader = arg;
struct concurrency_ctx *ctx = reader->ctx;
struct flb_aws_provider *provider = ctx->provider;
struct flb_aws_credentials *creds;
int akid;
int skid;
int token;
while (ctx->stop == FLB_FALSE) {
creds = provider->provider_vtable->get_credentials(provider);
if (!creds) {
ctx->null_creds[reader->id]++;
continue;
}
akid = credential_generation(creds->access_key_id, "akid_");
skid = credential_generation(creds->secret_access_key, "skid_");
token = credential_generation(creds->session_token, "token_");
if (akid < 0 || akid != skid || akid != token) {
ctx->torn_creds[reader->id]++;
}
ctx->reads[reader->id]++;
flb_aws_credentials_destroy(creds);
}
return NULL;
}
static void *concurrency_writer(void *arg)
{
struct concurrency_ctx *ctx = arg;
struct flb_aws_provider *provider = ctx->provider;
int i;
for (i = 0; i < CONCURRENCY_REFRESHES; i++) {
provider->provider_vtable->refresh(provider);
}
ctx->stop = FLB_TRUE;
return NULL;
struct concurrency_ctx {
struct flb_aws_provider *provider;
atomic_int stop;
/* results, one slot per reader so they need no synchronization */
int null_creds[CONCURRENCY_READERS];
int torn_creds[CONCURRENCY_READERS];
int reads[CONCURRENCY_READERS];
};
struct concurrency_reader_arg {
struct concurrency_ctx *ctx;
int id;
};
/* Returns the generation number in a "<prefix>_<n>" credential field */
static int credential_generation(const char *value, const char *prefix)
{
size_t prefix_len = strlen(prefix);
if (!value || strncmp(value, prefix, prefix_len) != 0) {
return -1;
}
return atoi(value + prefix_len);
}
static void *concurrency_reader(void *arg)
{
struct concurrency_reader_arg *reader = arg;
struct concurrency_ctx *ctx = reader->ctx;
struct flb_aws_provider *provider = ctx->provider;
struct flb_aws_credentials *creds;
int akid;
int skid;
int token;
while (atomic_load(&ctx->stop) == FLB_FALSE) {
creds = provider->provider_vtable->get_credentials(provider);
if (!creds) {
ctx->null_creds[reader->id]++;
continue;
}
akid = credential_generation(creds->access_key_id, "akid_");
skid = credential_generation(creds->secret_access_key, "skid_");
token = credential_generation(creds->session_token, "token_");
if (akid < 0 || akid != skid || akid != token) {
ctx->torn_creds[reader->id]++;
}
ctx->reads[reader->id]++;
flb_aws_credentials_destroy(creds);
}
return NULL;
}
static void *concurrency_writer(void *arg)
{
struct concurrency_ctx *ctx = arg;
struct flb_aws_provider *provider = ctx->provider;
int i;
for (i = 0; i < CONCURRENCY_REFRESHES; i++) {
provider->provider_vtable->refresh(provider);
}
atomic_store(&ctx->stop, FLB_TRUE);
return NULL;
🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 698-698: Avoid atoi/atol/atoll/atof: they perform no error detection, returning 0 on non-numeric input and invoking undefined behavior on out-of-range values. This rule flags every use of these functions regardless of data provenance. Convert strings with strtol/strtoul/strtod and check errno (and the endptr) so malformed or overflowing input is rejected.
Context: atoi(value + prefix_len)
Note: [CWE-20] Improper Input Validation.

(atoi-no-error-detection-c)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/internal/aws_credentials_sts.c` around lines 675 - 745, Make
concurrency_ctx.stop an atomic flag and use atomic loads in concurrency_reader
and an atomic store in concurrency_writer, preserving the existing loop
termination behavior. Update the test setup initialization alongside the
concurrency_ctx initialization to initialize the atomic value correctly,
eliminating the writer/readers data race.

Comment thread tests/internal/aws_credentials_sts.c
@Nahid-NHB
Nahid-NHB force-pushed the fix-12206-aws-creds-uaf branch from 0a9fdeb to 1e86b00 Compare August 3, 2026 22:11
@Nahid-NHB

Copy link
Copy Markdown
Contributor Author

Fixed the commit prefix, aws_credentials: -> aws:, and force pushed. commit_prefix_check.py passes locally now:

$ GITHUB_EVENT_NAME=pull_request GITHUB_BASE_REF=master python .github/scripts/commit_prefix_check.py
✅ Commit prefix validation passed.

Memory checking

I said in the description that I couldn't attach Valgrind output. Valgrind is installed on this machine, it just refuses to start without glibc debuginfo, which I can't install here:

valgrind:  Fatal error at startup: a function redirection
valgrind:  which is mandatory for this platform-tool combination
valgrind:  cannot be set up.
valgrind:  A must-be-redirected function
valgrind:  whose name matches the pattern:      memcmp
valgrind:  in an object with soname matching:   ld-linux-x86-64.so.2
valgrind:  was not found

So I built with -DFLB_SANITIZE_ADDRESS=On instead and ran the AWS credential tests under ASan with LeakSanitizer enabled. For a use-after-free across threads that's the better tool anyway, since Valgrind's memcheck serialises threads and would likely never hit the window.

Against the unpatched providers, ASan catches it on the first run and the stack is the one from the issue report:

==91711==ERROR: AddressSanitizer: heap-use-after-free on address 0x7b47839e5270
READ of size 7 at 0x7b47839e5270 thread T3
    #1 flb_sds_create src/flb_sds.c:86
    #2 get_credentials_fn_eks src/aws/flb_aws_credentials_sts.c:447
    #3 concurrency_reader tests/internal/aws_credentials_sts.c:713

0x7b47839e5270 is located 16 bytes inside of 23-byte region [0x7b47839e5260,0x7b47839e5277)
freed by thread T7 here:
    #2 flb_sds_destroy src/flb_sds.c:390
    #3 flb_aws_credentials_destroy src/aws/flb_aws_credentials.c:756
    #4 sts_assume_role_request src/aws/flb_aws_credentials_sts.c:777
    #5 assume_with_web_identity src/aws/flb_aws_credentials_sts.c:739
    #6 refresh_fn_eks src/aws/flb_aws_credentials_sts.c:484
    #7 concurrency_writer tests/internal/aws_credentials_sts.c:741

previously allocated by thread T0 here:
    #4 get_node src/aws/flb_aws_credentials_sts.c:951
    #5 flb_parse_sts_resp src/aws/flb_aws_credentials_sts.c:845
    #6 sts_assume_role_request src/aws/flb_aws_credentials_sts.c:764

Reader on T3 is in the unlocked copy at flb_aws_credentials_sts.c:447, writer on T7 freed it at :777 in sts_assume_role_request(). Those are the two lines the issue points at.

With the patch, all six AWS credential test binaries are clean, no ASan reports and no LeakSanitizer reports:

aws_credentials              PASS (no asan/lsan reports)
aws_credentials_ec2          PASS (no asan/lsan reports)
aws_credentials_http         PASS (no asan/lsan reports)
aws_credentials_profile      PASS (no asan/lsan reports)
aws_credentials_process      PASS (no asan/lsan reports)
aws_credentials_sts          PASS (no asan/lsan reports)

Run as ASAN_OPTIONS=detect_leaks=1 ./build-asan/bin/flb-it-<name>, exit code 0 for each, which with the default halt_on_error=1 means nothing was reported.

The plain (non-sanitized) build is also green, and the new concurrency test segfaults immediately without the patch (rc=139) and passes 10/10 with it.

If a maintainer wants actual Valgrind output on top of this I can set up a container with libc6-dbg and rerun, just let me know.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
tests/internal/aws_credentials_sts.c (2)

814-826: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Join only the threads that were created.

If pthread_create fails at Line 817, readers[i] stays uninitialized. Line 826 then passes an indeterminate pthread_t to pthread_join, which is undefined behavior. The same applies to writer at Line 821 and Line 824. Track the number of started threads, and join only those. If the writer fails to start, set the stop flag so the reader joins return.

This is separate from the accepted pattern of continuing after TEST_CHECK. The defect is the use of an uninitialized value.

🛡️ Proposed fix
+    int started = 0;
+
     for (i = 0; i < CONCURRENCY_READERS; i++) {
         args[i].ctx = &ctx;
         args[i].id = i;
         ret = pthread_create(&readers[i], NULL, concurrency_reader, &args[i]);
-        TEST_CHECK(ret == 0);
+        if (!TEST_CHECK(ret == 0)) {
+            break;
+        }
+        started++;
     }
 
     ret = pthread_create(&writer, NULL, concurrency_writer, &ctx);
-    TEST_CHECK(ret == 0);
-
-    pthread_join(writer, NULL);
-    for (i = 0; i < CONCURRENCY_READERS; i++) {
+    if (TEST_CHECK(ret == 0)) {
+        pthread_join(writer, NULL);
+    }
+    else {
+        /* stop the readers so the joins below return */
+        ctx.stop = FLB_TRUE;
+    }
+
+    for (i = 0; i < started; i++) {
         pthread_join(readers[i], NULL);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/internal/aws_credentials_sts.c` around lines 814 - 826, Update the
thread setup and cleanup around concurrency_reader and concurrency_writer to
track which threads successfully started, joining only those pthread_t values.
Handle writer creation failure by setting the shared stop flag so active readers
can exit and be joined safely, while preserving the existing TEST_CHECK
behavior.

Source: Learnings


712-712: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Synchronize the stop flag between the writer and the readers.

ctx.stop is a plain int. The writer stores it at Line 744. The readers load it at Line 712 without synchronization. The compiler can hoist the load out of the reader loop, so the readers can spin forever and pthread_join at Line 826 can hang the suite. TSan also reports this pair as a data race, which adds noise to the signal this test produces.

Use a C11 atomic for the flag, or guard it with a mutex.

🔒 Proposed fix using C11 atomics
+#include <stdatomic.h>
+
 struct concurrency_ctx {
     struct flb_aws_provider *provider;
-    int stop;
+    atomic_int stop;
-    while (ctx->stop == FLB_FALSE) {
+    while (atomic_load(&ctx->stop) == FLB_FALSE) {
-    ctx->stop = FLB_TRUE;
+    atomic_store(&ctx->stop, FLB_TRUE);

Also update Line 812:

-    ctx.stop = FLB_FALSE;
+    atomic_store(&ctx.stop, FLB_FALSE);

Also applies to: 744-744

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/internal/aws_credentials_sts.c` at line 712, Make the ctx->stop flag
atomic and use atomic loads in the reader loop and an atomic store where the
writer stops processing, including the cleanup/reset path around the referenced
line 812. Update the ctx field declaration and all accesses consistently so
pthread_join cannot hang and the test no longer reports a data race.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aws/flb_aws_credentials_sts.c`:
- Around line 110-133: Revalidate the cache after acquiring provider->lock in
the STS path around try_lock_provider: reread the refresh deadline and cached
credentials, and call sts_assume_role_request only when the cache is empty or
still expired; otherwise skip the redundant refresh while preserving credential
return behavior. Apply the same post-lock revalidation in
src/aws/flb_aws_credentials_sts.c lines 397-415 for the EKS path.
- Line 291: Check the return value of cache_lock pthread_mutex_init in the STS
and EKS paths at src/aws/flb_aws_credentials_sts.c:291 and :541, and in the
standard-chain cache initialization path. On failure, make
flb_sts_provider_create and the standard-chain path destroy provider->lock and
free provider; route EKS cleanup through flb_aws_provider_destroy so both
mutexes are destroyed.

---

Duplicate comments:
In `@tests/internal/aws_credentials_sts.c`:
- Around line 814-826: Update the thread setup and cleanup around
concurrency_reader and concurrency_writer to track which threads successfully
started, joining only those pthread_t values. Handle writer creation failure by
setting the shared stop flag so active readers can exit and be joined safely,
while preserving the existing TEST_CHECK behavior.
- Line 712: Make the ctx->stop flag atomic and use atomic loads in the reader
loop and an atomic store where the writer stops processing, including the
cleanup/reset path around the referenced line 812. Update the ctx field
declaration and all accesses consistently so pthread_join cannot hang and the
test no longer reports a data race.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8bc36b71-0763-425e-b6f6-6d98eebb59d2

📥 Commits

Reviewing files that changed from the base of the PR and between 0a9fdeb and 1e86b00.

📒 Files selected for processing (7)
  • include/fluent-bit/flb_aws_credentials.h
  • src/aws/flb_aws_credentials.c
  • src/aws/flb_aws_credentials_ec2.c
  • src/aws/flb_aws_credentials_http.c
  • src/aws/flb_aws_credentials_profile.c
  • src/aws/flb_aws_credentials_sts.c
  • tests/internal/aws_credentials_sts.c
🚧 Files skipped from review as they are similar to previous changes (5)
  • include/fluent-bit/flb_aws_credentials.h
  • src/aws/flb_aws_credentials_ec2.c
  • src/aws/flb_aws_credentials.c
  • src/aws/flb_aws_credentials_http.c
  • src/aws/flb_aws_credentials_profile.c

Comment thread src/aws/flb_aws_credentials_sts.c
Comment thread src/aws/flb_aws_credentials_sts.c
Every credential provider caches one flb_aws_credentials and hands out
copies of it. get_credentials() made that copy without holding the
provider lock, while the refresh path freed and replaced the cached
struct under it. That was fine when flushes were coroutines on a single
thread, but with `workers N` they run on N real pthreads, so the copy
and the swap can overlap.

Three ways it goes wrong: a reader dereferences a pointer the refresh
already freed and takes a SIGSEGV in flb_sds_create(), a reader lands
between `*creds = NULL` and the reassignment and bails out with no
credentials, or a reader picks up the access key from one refresh and
the secret from the next and gets InvalidSignatureException. All five
providers (sts, eks, ec2, http, profile) had the same shape.

The provider lock can't just be taken by readers: it is held across the
network call that fetches new credentials, which yields the coroutine,
so a second coroutine on the same thread blocking on it would deadlock.
Add a separate cache lock that is only ever held for the copy and for
the pointer swap, never across IO, and route both sides through
flb_aws_cache_get_credentials() and flb_aws_cache_set_credentials().
The swap now publishes the new credentials first and frees the old ones
afterwards, so there is no window where the cache is empty.

next_refresh is read and written under the same lock since it is part
of the same cached state.

Fixes fluent#12206

Signed-off-by: Md Nahid Hassan Bhuiyan <nahid151341@gmail.com>
Six threads read credentials from an EKS provider while a seventh
refreshes it in a loop. The mock STS response tags the access key,
secret key and session token with the same generation number, so a
reader that stitched together two different refreshes is detectable
without a sanitizer.

Against the unfixed providers this segfaults within a second, the same
crash reported in fluent#12206. Under ASan or TSan it also flags the
use-after-free directly.

Signed-off-by: Md Nahid Hassan Bhuiyan <nahid151341@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
tests/internal/aws_credentials_sts.c (2)

675-745: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Synchronize the stop flag between the writer and the readers.

ctx.stop is a plain int. The writer stores it at Line 744. The readers load it at Line 712 without synchronization. The compiler can hoist the load out of the reader loop, because nothing in the loop body touches ctx.stop. The readers then never exit and pthread_join at Line 826 hangs the suite. The comment at Lines 668-669 also asks the maintainer to run this test under TSan, and TSan reports this store/load pair as a data race.

Use a C11 atomic for the flag, or guard it with a mutex.

🔒 Proposed fix using C11 atomics
+#include <stdatomic.h>
+
 struct concurrency_ctx {
     struct flb_aws_provider *provider;
-    int stop;
+    atomic_int stop;
-    while (ctx->stop == FLB_FALSE) {
+    while (atomic_load(&ctx->stop) == FLB_FALSE) {
-    ctx->stop = FLB_TRUE;
+    atomic_store(&ctx->stop, FLB_TRUE);

Also update the initialization at Line 812.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/internal/aws_credentials_sts.c` around lines 675 - 745, Make
concurrency_ctx.stop an atomic flag and use atomic load/store operations in
concurrency_reader and concurrency_writer so readers reliably observe
termination without a data race. Update the stop initialization in the test
setup to use the atomic initialization form, preserving the existing reader loop
and writer completion behavior.

814-826: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Do not join threads that were not created.

If pthread_create fails at Line 817, readers[i] stays uninitialized. Line 826 then passes an indeterminate pthread_t to pthread_join, which is undefined behavior. The same applies to writer at Line 821 and Line 824. Track the number of threads that started, and join only those. If the writer fails to start, set ctx.stop so the readers exit.

🛡️ Proposed fix
+    int started = 0;
+
     for (i = 0; i < CONCURRENCY_READERS; i++) {
         args[i].ctx = &ctx;
         args[i].id = i;
         ret = pthread_create(&readers[i], NULL, concurrency_reader, &args[i]);
-        TEST_CHECK(ret == 0);
+        if (!TEST_CHECK(ret == 0)) {
+            break;
+        }
+        started++;
     }
 
     ret = pthread_create(&writer, NULL, concurrency_writer, &ctx);
-    TEST_CHECK(ret == 0);
-
-    pthread_join(writer, NULL);
-    for (i = 0; i < CONCURRENCY_READERS; i++) {
+    if (TEST_CHECK(ret == 0)) {
+        pthread_join(writer, NULL);
+    }
+    else {
+        ctx.stop = FLB_TRUE;
+    }
+
+    for (i = 0; i < started; i++) {
         pthread_join(readers[i], NULL);

This is separate from the accepted pattern of continuing after TEST_CHECK. The problem is the use of an indeterminate value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/internal/aws_credentials_sts.c` around lines 814 - 826, Update the
thread setup and cleanup around concurrency_reader and concurrency_writer to
track which pthread_create calls succeed, join only successfully created
threads, and avoid passing uninitialized pthread_t values to pthread_join. If
pthread_create fails for writer, set ctx.stop so active readers terminate;
preserve the existing TEST_CHECK behavior for reporting failures.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@tests/internal/aws_credentials_sts.c`:
- Around line 675-745: Make concurrency_ctx.stop an atomic flag and use atomic
load/store operations in concurrency_reader and concurrency_writer so readers
reliably observe termination without a data race. Update the stop initialization
in the test setup to use the atomic initialization form, preserving the existing
reader loop and writer completion behavior.
- Around line 814-826: Update the thread setup and cleanup around
concurrency_reader and concurrency_writer to track which pthread_create calls
succeed, join only successfully created threads, and avoid passing uninitialized
pthread_t values to pthread_join. If pthread_create fails for writer, set
ctx.stop so active readers terminate; preserve the existing TEST_CHECK behavior
for reporting failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5abc0d11-ab44-4307-b488-270c0babbd7f

📥 Commits

Reviewing files that changed from the base of the PR and between 1e86b00 and 1492bd8.

📒 Files selected for processing (7)
  • include/fluent-bit/flb_aws_credentials.h
  • src/aws/flb_aws_credentials.c
  • src/aws/flb_aws_credentials_ec2.c
  • src/aws/flb_aws_credentials_http.c
  • src/aws/flb_aws_credentials_profile.c
  • src/aws/flb_aws_credentials_sts.c
  • tests/internal/aws_credentials_sts.c
🚧 Files skipped from review as they are similar to previous changes (6)
  • include/fluent-bit/flb_aws_credentials.h
  • src/aws/flb_aws_credentials_http.c
  • src/aws/flb_aws_credentials_sts.c
  • src/aws/flb_aws_credentials_profile.c
  • src/aws/flb_aws_credentials.c
  • src/aws/flb_aws_credentials_ec2.c

@Nahid-NHB

Copy link
Copy Markdown
Contributor Author

The unit test job failure here is flb-rt-out_loki, not anything in this PR. Both the gcc and clang runs fail on that one test and pass the other 199, and all the AWS credential tests pass:

Test #192: flb-it-aws_credentials ...........  Passed  0.01 sec
Test #193: flb-it-aws_credentials_ec2 .......  Passed  0.02 sec
Test #194: flb-it-aws_credentials_sts .......  Passed  0.11 sec
Test #195: flb-it-aws_credentials_http ......  Passed  6.03 sec
Test #196: flb-it-aws_credentials_profile ...  Passed  0.02 sec
Test #197: flb-it-aws_credentials_process ...  Passed  0.03 sec

99% tests passed, 1 tests failed out of 200
The following tests FAILED:
	 73 - flb-rt-out_loki (Failed)

Looks like the known flakiness that #12218 is fixing: the Loki mock server only handles FLB_ENGINE_EV_CUSTOM and misses FLB_ENGINE_EV_THREAD, so tenant-B requests never get resumed. The subtest failing here is tenant_id_key_splits_requests, which matches. Happy to rebase once that lands if it makes the run green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

aws credentials: use-after-free when refresh races get_credentials() leading to SIGSEGV with multi-worker outputs (EKS/IRSA)

1 participant