Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Windows: fix HTTP rate limit response header parsing. ([#1732](https://github.com/getsentry/sentry-native/pull/1732))
- POSIX: prevent condition-variable timeout overflow from busy-spinning flush and shutdown waits. ([#1731](https://github.com/getsentry/sentry-native/pull/1731))
- Native/macOS: fix thread stack descriptor. ([#1726](https://github.com/getsentry/sentry-native/pull/1726))
- Cap rate-limit retry-after values at 24 hours to prevent a MITM-provided response from disabling event delivery for the process lifetime. ([#1744](https://github.com/getsentry/sentry-native/pull/1744))

## 0.14.2

Expand Down
16 changes: 12 additions & 4 deletions src/sentry_ratelimiter.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@
#include "sentry_utils.h"

#define MAX_RATE_LIMITS 4
#define MAX_RETRY_AFTER (24 * 60 * 60) // 24h

struct sentry_rate_limiter_s {
uint64_t disabled_until[MAX_RATE_LIMITS];
};

static uint64_t
calculate_disabled_until(uint64_t retry_after)
{
if (retry_after > MAX_RETRY_AFTER) {
retry_after = MAX_RETRY_AFTER;
}
return sentry__monotonic_time() + retry_after * 1000; // ms
}

sentry_rate_limiter_t *
sentry__rate_limiter_new(void)
{
Expand All @@ -34,8 +44,7 @@ sentry__rate_limiter_update_from_header(
if (!sentry__slice_consume_uint64(&slice, &retry_after)) {
return false;
}
retry_after *= 1000;
retry_after += sentry__monotonic_time();
retry_after = calculate_disabled_until(retry_after);

if (!sentry__slice_consume_if(&slice, ':')) {
return false;
Expand Down Expand Up @@ -79,8 +88,7 @@ sentry__rate_limiter_update_from_http_retry_after(
sentry_slice_t slice = sentry__slice_from_str(retry_after);
uint64_t eta = 60;
sentry__slice_consume_uint64(&slice, &eta);
rl->disabled_until[SENTRY_RL_CATEGORY_ANY]
= sentry__monotonic_time() + eta * 1000;
rl->disabled_until[SENTRY_RL_CATEGORY_ANY] = calculate_disabled_until(eta);
return true;
}

Expand Down
30 changes: 30 additions & 0 deletions tests/unit/test_ratelimiter.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,33 @@ SENTRY_TEST(rate_limit_parsing)

sentry__rate_limiter_free(rl);
}

SENTRY_TEST(rate_limit_retry_after)
{
const uint64_t max_retry_after = 24 * 60 * 60 * 1000;

sentry_rate_limiter_t *rl = sentry__rate_limiter_new();
TEST_ASSERT(!!rl);

uint64_t before = sentry__monotonic_time();
TEST_CHECK(
sentry__rate_limiter_update_from_header(rl, "999999999999999:error::"));
uint64_t after = sentry__monotonic_time();

uint64_t disabled_until
= sentry__rate_limiter_get_disabled_until(rl, SENTRY_RL_CATEGORY_ERROR);
TEST_CHECK(disabled_until >= before + max_retry_after);
TEST_CHECK(disabled_until <= after + max_retry_after);

before = sentry__monotonic_time();
TEST_CHECK(sentry__rate_limiter_update_from_http_retry_after(
rl, "999999999999999"));
after = sentry__monotonic_time();

disabled_until
= sentry__rate_limiter_get_disabled_until(rl, SENTRY_RL_CATEGORY_ANY);
TEST_CHECK(disabled_until >= before + max_retry_after);
TEST_CHECK(disabled_until <= after + max_retry_after);

sentry__rate_limiter_free(rl);
}
1 change: 1 addition & 0 deletions tests/unit/tests.inc
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ XX(procmaps_parser)
XX(propagation_context_init)
XX(query_consent_requirement)
XX(rate_limit_parsing)
XX(rate_limit_retry_after)
XX(raw_envelope_event_id)
XX(read_envelope_from_file)
XX(read_write_envelope_to_file_null)
Expand Down
Loading