diff --git a/compiler-rt/lib/scudo/standalone/atomic_helpers.h b/compiler-rt/lib/scudo/standalone/atomic_helpers.h --- a/compiler-rt/lib/scudo/standalone/atomic_helpers.h +++ b/compiler-rt/lib/scudo/standalone/atomic_helpers.h @@ -120,6 +120,13 @@ __ATOMIC_RELAXED); } +template +inline bool atomic_compare_exchange_weak(volatile T *A, typename T::Type *Cmp, + typename T::Type Xchg, + memory_order MO) { + return atomic_compare_exchange_strong(A, Cmp, Xchg, MO); +} + // Clutter-reducing helpers. template diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h --- a/compiler-rt/lib/scudo/standalone/combined.h +++ b/compiler-rt/lib/scudo/standalone/combined.h @@ -162,6 +162,10 @@ systemSupportsMemoryTagging()) Primary.Options.set(OptionBit::UseMemoryTagging); Primary.Options.set(OptionBit::UseOddEvenTags); + if (getFlags()->hard_rss_limit_mb || getFlags()->soft_rss_limit_mb) { + Primary.Options.set(OptionBit::CheckRssLimit); + atomic_store_relaxed(&RssLastCheckedAtNS, getMonotonicTime()); + } QuarantineMaxChunkSize = static_cast(getFlags()->quarantine_max_chunk_size); @@ -346,6 +350,15 @@ } DCHECK_LE(Size, NeededSize); + if ( (Options.get(OptionBit::CheckRssLimit)) && UNLIKELY(isRssLimitExceeded())) { + if (Options.get(OptionBit::MayReturnNull)) + return nullptr; + const u64 CurrentRssMb = GetRSS() >> 20; + u64 SoftRssLimitMb = (u64)getFlags()->soft_rss_limit_mb; + reportRSSCheck("Soft RSS limit exhausted (Limit x Current): ", + SoftRssLimitMb, CurrentRssMb); + } + void *Block = nullptr; uptr ClassId = 0; uptr SecondaryBlockEnd = 0; @@ -671,6 +684,55 @@ return NewPtr; } +// Opportunistic RSS limit check. This will update the RSS limit status, if +// it can, every 250ms, otherwise it will just return the current one. +#if SCUDO_LINUX + NOINLINE bool isRssLimitExceeded() { + u64 LastCheck = atomic_load_relaxed(&RssLastCheckedAtNS); + const u64 CurrentCheck = getMonotonicTime(); + u64 HardRssLimitMb = (u64)getFlags()->hard_rss_limit_mb; + u64 SoftRssLimitMb = (u64)getFlags()->soft_rss_limit_mb; + + if (LIKELY(CurrentCheck < LastCheck + (250ULL * 1000000ULL))) + return atomic_load_relaxed(&RssLimitExceeded); + + if (!atomic_compare_exchange_weak(&RssLastCheckedAtNS, &LastCheck, + CurrentCheck, memory_order_relaxed)) + return atomic_load_relaxed(&RssLimitExceeded); + + // TODO: We currently use sanitizer_common's GetRSS which reads the + // RSS from /proc/self/statm by default. We might want to + // call getrusage directly, even if it's less accurate. + const u64 CurrentRssMb = GetRSS() >> 20; + + if (HardRssLimitMb && UNLIKELY(HardRssLimitMb < CurrentRssMb)) { + reportRSSCheck("Hard RSS limit exhausted (Limit x Current): ", + HardRssLimitMb, CurrentRssMb); + + } + + if (SoftRssLimitMb) { + if (atomic_load_relaxed(&RssLimitExceeded)) { + if (CurrentRssMb <= SoftRssLimitMb) + atomic_store_relaxed(&RssLimitExceeded, false); + } else { + if (CurrentRssMb > SoftRssLimitMb) { + atomic_store_relaxed(&RssLimitExceeded, true); + } + } + } + + return atomic_load_relaxed(&RssLimitExceeded); + } +#else + // We redefine as ALWAYS_INLINE for platforms that are not supported + // The compiler optimizes out the calling of the function and the entire + // check from the caller, so no symbol lookup problems for GetRSS either + ALWAYS_INLINE bool isRssLimitExceeded() { + return 0; + } +#endif + // TODO(kostyak): disable() is currently best-effort. There are some small // windows of time when an allocation could still succeed after // this function finishes. We will revisit that later. @@ -994,6 +1056,8 @@ QuarantineT Quarantine; TSDRegistryT TSDRegistry; pthread_once_t PostInitNonce = PTHREAD_ONCE_INIT; + atomic_u64 RssLastCheckedAtNS={}; + atomic_u8 RssLimitExceeded={}; #ifdef GWP_ASAN_HOOKS gwp_asan::GuardedPoolAllocator GuardedAlloc; diff --git a/compiler-rt/lib/scudo/standalone/common.h b/compiler-rt/lib/scudo/standalone/common.h --- a/compiler-rt/lib/scudo/standalone/common.h +++ b/compiler-rt/lib/scudo/standalone/common.h @@ -132,6 +132,8 @@ const char *getEnv(const char *Name); +u64 GetRSS(); + u64 getMonotonicTime(); u32 getThreadID(); diff --git a/compiler-rt/lib/scudo/standalone/common.cpp b/compiler-rt/lib/scudo/standalone/common.cpp --- a/compiler-rt/lib/scudo/standalone/common.cpp +++ b/compiler-rt/lib/scudo/standalone/common.cpp @@ -35,4 +35,10 @@ die(); } +#if ! SCUDO_LINUX +u64 GetRSS() { + return 0; +} +#endif + } // namespace scudo diff --git a/compiler-rt/lib/scudo/standalone/flags.cpp b/compiler-rt/lib/scudo/standalone/flags.cpp --- a/compiler-rt/lib/scudo/standalone/flags.cpp +++ b/compiler-rt/lib/scudo/standalone/flags.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "internal_defs.h" #include "flags.h" #include "common.h" #include "flags_parser.h" diff --git a/compiler-rt/lib/scudo/standalone/flags.inc b/compiler-rt/lib/scudo/standalone/flags.inc --- a/compiler-rt/lib/scudo/standalone/flags.inc +++ b/compiler-rt/lib/scudo/standalone/flags.inc @@ -45,3 +45,13 @@ SCUDO_FLAG(int, release_to_os_interval_ms, SCUDO_ANDROID ? INT32_MIN : 5000, "Interval (in milliseconds) at which to attempt release of unused " "memory to the OS. Negative values disable the feature.") + +SCUDO_FLAG(int, hard_rss_limit_mb, 0, + "LINUX-ONLY: Hard RSS Limit in Mb" + "If non-zero, once the limit is achieved, abort the process") + +SCUDO_FLAG(int, soft_rss_limit_mb, 0, + "LINUX_ONLY: Soft RSS Limit in Mb" + "If non-zero, once the limit is reached, all subsequent calls will fail or return NULL" + "until the RSS goes below the soft limit") + diff --git a/compiler-rt/lib/scudo/standalone/internal_defs.h b/compiler-rt/lib/scudo/standalone/internal_defs.h --- a/compiler-rt/lib/scudo/standalone/internal_defs.h +++ b/compiler-rt/lib/scudo/standalone/internal_defs.h @@ -88,6 +88,15 @@ typedef int16_t s16; typedef int32_t s32; typedef int64_t s64; +typedef int fd_t; + +enum FileAccessMode { + RdOnly, + WrOnly, + RdWr +}; + +#define kInvalidFd ((fd_t) -1) // The following two functions have platform specific implementations. void outputRaw(const char *Buffer); @@ -103,6 +112,8 @@ #define RAW_CHECK(Expr) RAW_CHECK_MSG(Expr, #Expr) +void NORETURN reportRSSCheck(const char *Message, u64 RssLimit, u64 RssCurrent); + void NORETURN reportCheckFailed(const char *File, int Line, const char *Condition, u64 Value1, u64 Value2); #define CHECK_IMPL(C1, Op, C2) \ diff --git a/compiler-rt/lib/scudo/standalone/linux.cpp b/compiler-rt/lib/scudo/standalone/linux.cpp --- a/compiler-rt/lib/scudo/standalone/linux.cpp +++ b/compiler-rt/lib/scudo/standalone/linux.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #if SCUDO_ANDROID #include @@ -168,7 +169,7 @@ #endif // defined(SYS_getrandom) // Up to 256 bytes, a read off /dev/urandom will not be interrupted. // Blocking is moot here, O_NONBLOCK has no effect when opening /dev/urandom. - const int FileDesc = open("/dev/urandom", O_RDONLY); + const int FileDesc = open("/dev/urandom", RdOnly); if (FileDesc == -1) return false; ReadBytes = read(FileDesc, Buffer, Length); @@ -180,6 +181,44 @@ extern "C" WEAK int async_safe_write_log(int pri, const char *tag, const char *msg); +s64 internal_read(fd_t fd, char *buf, size_t size) { + return read(fd, buf, size); +} + +void internal_close(fd_t fd) { + close(fd); +} + +fd_t OpenFile(const char *FileName, int flags) { + return open(FileName, flags); +} + +u64 GetRSS() { + fd_t fd = OpenFile("/proc/self/statm", RdOnly); + char buf[64]; + s64 len = internal_read(fd, buf, sizeof(buf) - 1); + internal_close(fd); + if (len <= 0) + return 0; + buf[len] = 0; + + // The format of the file is: + // 1084 89 69 11 0 79 0 + // We need the second number which is RSS in pages. + char *pos = buf; + // Skip the first number. + while (*pos >= '0' && *pos <= '9') + pos++; + // Skip whitespaces. + while (!(*pos >= '0' && *pos <= '9') && *pos != 0) + pos++; + // Read the number. + u64 rss = 0; + while (*pos >= '0' && *pos <= '9') + rss = rss * 10 + (u64)(*pos)++ - '0'; + return rss * getPageSizeCached(); +} + void outputRaw(const char *Buffer) { if (&async_safe_write_log) { constexpr s32 AndroidLogInfo = 4; diff --git a/compiler-rt/lib/scudo/standalone/options.h b/compiler-rt/lib/scudo/standalone/options.h --- a/compiler-rt/lib/scudo/standalone/options.h +++ b/compiler-rt/lib/scudo/standalone/options.h @@ -25,6 +25,7 @@ UseOddEvenTags, UseMemoryTagging, AddLargeAllocationSlack, + CheckRssLimit, }; struct Options { diff --git a/compiler-rt/lib/scudo/standalone/report.cpp b/compiler-rt/lib/scudo/standalone/report.cpp --- a/compiler-rt/lib/scudo/standalone/report.cpp +++ b/compiler-rt/lib/scudo/standalone/report.cpp @@ -36,6 +36,11 @@ inline void NORETURN trap() { __builtin_trap(); } +void NORETURN reportRSSCheck(const char *Message, u64 RssLimit, u64 RssCurrent) { + ScopedErrorReport Report; + Report.append("%s %d x %d\n", Message, RssLimit, RssCurrent); +} + // This could potentially be called recursively if a CHECK fails in the reports. void NORETURN reportCheckFailed(const char *File, int Line, const char *Condition, u64 Value1, u64 Value2) {