-
Notifications
You must be signed in to change notification settings - Fork 60
Parameterize offline-storage deletes and harden kill-switch response handling #1467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b95dd41
e251cb6
1be1001
048709c
7838af5
c781903
13aaeeb
8e97535
4149075
1bdb878
986c9b4
b7abaee
e2bf622
d5c7706
be88816
729b0ab
89907b0
5327490
c5547c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,9 +7,12 @@ | |
|
|
||
| #include "pal/PAL.hpp" | ||
|
|
||
| #include <list> | ||
| #include <map> | ||
| #include <string> | ||
| #include <mutex> | ||
| #include <stdexcept> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include <atomic> | ||
|
|
||
|
|
@@ -39,8 +42,8 @@ namespace MAT_NS_BEGIN { | |
| std::string timeStr = headers.get("Retry-After"); | ||
| if (!timeStr.empty()) | ||
| { | ||
| int64_t timeinSecs = std::stoi(timeStr); | ||
| if (timeinSecs > 0) | ||
| int64_t timeinSecs = 0; | ||
| if (tryParseSeconds(timeStr, timeinSecs) && timeinSecs > 0) | ||
| { | ||
| std::lock_guard<std::mutex> guard(m_lock); | ||
| m_retryAfterExpiryTime = PAL::getUtcSystemTime() + timeinSecs; | ||
|
|
@@ -64,14 +67,22 @@ namespace MAT_NS_BEGIN { | |
| // Strip suffix and assume ':all' events of that tenant are killed | ||
| token.erase(pos, token.length() - pos); | ||
| } | ||
| // Reject kill-tokens with control characters (defensive | ||
| // hygiene; see isValidTenantToken). Any other opaque value is | ||
| // safe to act on and must remain killable -- over-restricting | ||
| // would let that tenant escape the kill. | ||
| if (!isValidTenantToken(token)) | ||
| { | ||
| continue; | ||
| } | ||
| killtokensVector.push_back(token); | ||
| } | ||
|
|
||
| int64_t timeinSecs = 0; | ||
| std::string timeString = headers.get("kill-duration"); | ||
| if (!timeString.empty()) | ||
| { | ||
| timeinSecs = std::stoi(timeString); | ||
| tryParseSeconds(timeString, timeinSecs); | ||
| } | ||
|
|
||
| if (killtokensVector.size() > 0 && timeinSecs > 0) | ||
|
bmehta001 marked this conversation as resolved.
|
||
|
|
@@ -156,6 +167,101 @@ namespace MAT_NS_BEGIN { | |
| } | ||
|
|
||
| private: | ||
| // Parse a count of seconds from a response-header value (Retry-After / | ||
| // kill-duration). Returns false when the value is malformed or out of | ||
| // range instead of letting std::stoll throw: the worker thread that drives | ||
| // handleResponse has no exception guard, so a throw here would crash the | ||
| // process. | ||
| // | ||
| // RFC 7231 allows Retry-After to be either delay-seconds or an HTTP-date. | ||
| // We deliberately accept only delay-seconds and ignore the HTTP-date form: | ||
| // - The collector this SDK targets sends Retry-After as delay-seconds, so | ||
| // the date form does not occur in practice. | ||
| // - An HTTP-date is absolute and would have to be converted to a delay | ||
| // against the device clock, which a telemetry SDK cannot trust (it has | ||
| // a separate clock-skew channel for exactly this reason); a skewed clock | ||
| // would yield a wrong, negative, or huge back-off. | ||
| // - Parsing it would add an error-prone three-format date parser on this | ||
| // no-exception-guard worker thread for no practical benefit. | ||
| // Ignoring an unparseable value degrades safely: the SDK's own retry | ||
| // back-off still applies; only the server's exact instant is not honored. | ||
| // | ||
| // RFC 7231 delay-seconds is 1*DIGIT, optionally surrounded by HTTP optional | ||
| // whitespace (OWS = SP / HTAB). We trim only OWS and require the remaining | ||
| // value to be all digits. This rejects malformed inputs that std::stoll | ||
| // would otherwise accept -- a leading '+'/'-' sign, or leading CR/LF and | ||
| // other control whitespace that stoll silently skips -- and keeps leading | ||
| // and trailing handling symmetric. | ||
| static bool tryParseSeconds(const std::string& value, int64_t& outSeconds) | ||
| { | ||
| outSeconds = 0; | ||
| size_t begin = 0; | ||
| size_t end = value.size(); | ||
| while (begin < end && (value[begin] == ' ' || value[begin] == '\t')) | ||
| { | ||
| ++begin; | ||
| } | ||
| while (end > begin && (value[end - 1] == ' ' || value[end - 1] == '\t')) | ||
| { | ||
| --end; | ||
| } | ||
| if (begin == end) | ||
| { | ||
| return false; | ||
| } | ||
| for (size_t i = begin; i < end; ++i) | ||
| { | ||
| if (value[i] < '0' || value[i] > '9') | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
| try | ||
| { | ||
| // Only std::out_of_range can fire now (the substring is all digits); | ||
| // catching it ignores an over-large value rather than crashing. | ||
| const long long parsed = std::stoll(value.substr(begin, end - begin)); | ||
|
bmehta001 marked this conversation as resolved.
|
||
| // Clamp to a value that cannot overflow when later added to a current | ||
| // UTC timestamp (seconds) to compute an expiry time. No legitimate | ||
| // Retry-After / kill-duration approaches this; an absurd value is | ||
| // capped instead of wrapping the expiry into the past. | ||
| const int64_t kMaxSeconds = 100LL * 365 * 24 * 60 * 60; // ~100 years | ||
| outSeconds = (parsed > kMaxSeconds) ? kMaxSeconds : static_cast<int64_t>(parsed); | ||
| return true; | ||
| } | ||
| catch (const std::exception&) | ||
| { | ||
| return false; | ||
| } | ||
|
bmehta001 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // Tenant tokens are opaque (they may legitimately contain spaces, quotes, | ||
| // etc.). Every sink that consumes the token handles raw bytes safely -- the | ||
| // offline-storage DELETE is parameterized (SQLite bind / Room DAO) and the | ||
| // in-memory match is a plain string compare -- so any printable value must | ||
| // remain killable; over-restricting would let that tenant escape the kill. | ||
| // We still reject control characters and over-long values as defensive | ||
| // hygiene: a legitimate token never contains them, an embedded NUL would be | ||
| // truncated by the Room JNI NewStringUTF(c_str()) call (acting on the wrong | ||
| // token), and it keeps the value safe if it ever reaches a log/display sink. | ||
| static bool isValidTenantToken(const std::string& token) | ||
| { | ||
| if (token.empty() || token.size() > 256) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have an authoritative 256-byte max for tenant tokens? I don't see the same limit enforced when apps create loggers, so this could make a valid stored tenant impossible to kill if its token is longer than 256 bytes.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have messaged the Collector team to ask - I was just keeping a large upper bound bc my token is 74 bytes, but I don't actually know if very long tokens could be possible. |
||
| { | ||
| return false; | ||
| } | ||
| for (unsigned char c : token) | ||
| { | ||
| // Reject C0 control characters and DEL; allow any other byte | ||
| // (printable ASCII and UTF-8 sequences). | ||
| if (c < 0x20 || c == 0x7f) | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| std::map<std::string, int64_t> m_tokenTime; | ||
| std::mutex m_lock; | ||
| bool m_isRetryAfterActive; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.