Skip to content

Fix JNI string lifetime/null-safety, Windows cancel-drain race, and UWP version parse#1494

Open
bmehta001 wants to merge 4 commits into
microsoft:mainfrom
bmehta001:bhamehta/fix-jni-windows-uwp-safety
Open

Fix JNI string lifetime/null-safety, Windows cancel-drain race, and UWP version parse#1494
bmehta001 wants to merge 4 commits into
microsoft:mainfrom
bmehta001:bhamehta/fix-jni-windows-uwp-safety

Conversation

@bmehta001

Copy link
Copy Markdown
Contributor

Bundles four small, latent safety fixes from a repo-wide material-bug review. None are recent regressions — they were introduced between 2018 and 2023.

1. Signals_jni.cpp — use-after-free (🟠)

sendSignal called ReleaseStringUTFChars on the UTF buffer before passing it to Signals::CreateEventProperties (which copies from it by value), reading freed/unpinned memory. Moved the release to after the buffer is consumed.

2. HttpClient_WinInet.cpp / HttpClient_WinRt.cpp — data race (🟡)

The CancelAllRequests() drain loop read m_requests.empty() with no lock held, while erase() mutates the std::map on the WinInet callback / PPL continuation thread under m_requestsMutex — a data race / UB. Now reads empty() under the lock each iteration, mirroring the already-correct WinRt destructor drain. (The recent #1460 fixed the destructor's drain but not these methods.)

3. JniConvertors.cpp / OfflineStorage_Room.cpp — null deref (🟢)

GetStringUTFChars returns null on allocation failure; the result was fed straight into a std::string constructor. Added a null check before constructing.

4. WindowsRuntimeSystemInformationImpl.cpp — UWP parse (🟢)

std::stoull on the DeviceFamilyVersion string was unguarded (would throw on malformed/oversized input). Guarded with try/catch defaulting to 0 (the code already has a versionDec == 0"10.0" fallback).

Validation

JniConvertors.cpp and OfflineStorage_Room.cpp pass NDK aarch64 -fsyntax-only. Signals_jni (needs the private signals module), the Windows HTTP clients, and the UWP path can't be built on the dev host and rely on Android/Windows CI; the WinInet/WinRt change mirrors the existing correct destructor pattern in the same files.

…WP parse)

Bundled small fixes from a repo-wide review. None are recent regressions
(introduced 2018-2023).

1) Signals_jni.cpp (UAF): sendSignal called ReleaseStringUTFChars on the
   UTF buffer *before* passing it to Signals::CreateEventProperties (which
   copies from it by value), reading freed/unpinned memory. Move the
   release to after the buffer is consumed.

2) HttpClient_WinInet.cpp / HttpClient_WinRt.cpp (data race): the
   CancelAllRequests() drain loop read `m_requests.empty()` with no lock
   held, while erase() mutates the map on the HTTP callback / PPL
   continuation thread under m_requestsMutex -- a data race / UB on
   std::map. Read empty() under the lock each iteration, mirroring the
   already-correct WinRt destructor drain. (The recent microsoft#1460 fixed the
   destructor's drain but not these methods.)

3) JniConvertors.cpp / OfflineStorage_Room.cpp (null deref): the result
   of GetStringUTFChars (which returns null on allocation failure) was
   fed straight into a std::string ctor. Null-check before constructing.

4) WindowsRuntimeSystemInformationImpl.cpp (UWP): std::stoull on the
   DeviceFamilyVersion string was unguarded; guard it with try/catch
   defaulting to 0 (the code already has a versionDec==0 -> "10.0"
   fallback).

Validation: JniConvertors.cpp and OfflineStorage_Room.cpp pass NDK
aarch64 -fsyntax-only. Signals_jni (needs the private signals module),
the Windows HTTP clients, and the UWP path can't be built on this host
and rely on the Android/Windows CI; the WinInet/WinRt fix mirrors the
existing correct destructor pattern in the same files.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bmehta001 bmehta001 requested a review from a team as a code owner June 22, 2026 16:53
Comment thread lib/jni/Signals_jni.cpp
Comment thread lib/offline/OfflineStorage_Room.cpp Outdated
lib/jni/Signals_jni.cpp (sendSignal): guard the jstring argument and the
GetStringUTFChars() result for null before use. A null return (e.g. OOM, with a
pending exception) previously flowed into CreateEventProperties()/Release as a
null pointer. Now returns false instead.

lib/offline/OfflineStorage_Room.cpp (ReleaseRecords): a failed tenant-token
string read was turned into an empty std::string and reported as
dropped[""] = count, silently misattributing dropped records to an empty tenant
token. Now follows the file's established pattern (GetStringUTFChars + ThrowRuntime)
and skips the entry when the read fails instead of fabricating an empty token.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR bundles several small safety fixes across the Android JNI layer and Windows-specific implementations to eliminate latent undefined behavior (use-after-free / data race), improve null-safety around JNI string conversion, and harden UWP OS version parsing against malformed input.

Changes:

  • Fix JNI string lifetime in Signals_sendSignal and add null checks for GetStringUTFChars failures before constructing std::string.
  • Remove a Windows HTTP cancel/drain data race by checking m_requests.empty() under the same mutex used by erase().
  • Guard UWP DeviceFamilyVersion parsing (std::stoull) with exception handling and preserve the existing versionDec == 0 fallback behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
lib/jni/Signals_jni.cpp Prevents using a UTF buffer after it has been released; adds null-safety for null jstring / failed UTF conversion.
lib/jni/JniConvertors.cpp Avoids constructing std::string from a null UTF pointer when GetStringUTFChars fails.
lib/offline/OfflineStorage_Room.cpp Adds a null check before converting a UTF pointer to std::string, preventing a null deref / misattribution.
lib/http/HttpClient_WinRt.cpp Fixes a drain-loop data race by reading m_requests.empty() under m_requestsMutex.
lib/http/HttpClient_WinInet.cpp Fixes a drain-loop data race by reading m_requests.empty() under m_requestsMutex.
lib/pal/universal/WindowsRuntimeSystemInformationImpl.cpp Hardens UWP version parsing by catching std::stoull exceptions and falling back to 0.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Addresses @lalitb's review: Signals_jni nativeInitialize called
strlen(convertedValue) (and ReleaseStringUTFChars) on the result of
GetStringUTFChars(base_url, ...) with no null check, so a failed
conversion (e.g. pending OOM) would dereference null. Guard it: only
read/release when non-null; an empty/failed base_url keeps the existing
default (BaseUrl left unset).

While here, make the JNI string null-safety consistent across the file
this PR already hardens. ThrowRuntime/ThrowLogic only throw when
s_throwExceptions is true; otherwise they ExceptionClear() and execution
continues, so the existing checks are not sufficient on their own. The
other GetStringUTFChars sites in OfflineStorage_Room.cpp still used the
pointer unguarded:
 - GetReservedRecords: token_utf passed straight into StorageRecord and
   ReleaseStringUTFChars (null -> std::string(nullptr) UB / release of
   null).
 - GetSetting: result = utf with no null check.
 - GetRecords: tenant_utf passed into emplace_back / released unguarded.
Each now mirrors the guard already added for the dropped-records path:
substitute an empty token (matching JniConvertors' canonical return ""
on null) and only release when non-null. This avoids null deref and
release-of-null without silently changing behavior on success.

Validated with NDK r29 clang -fsyntax-only (aarch64-linux-android23,
-std=c++14, JNI build defines) on both files: clean.

Files changed:
- lib/jni/Signals_jni.cpp: null-guard base_url GetStringUTFChars
- lib/offline/OfflineStorage_Room.cpp: null-guard token_utf, result, tenant_utf

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread lib/jni/Signals_jni.cpp
Comment on lines 64 to +66
jboolean isCopy = true;
const char *convertedValue = (env)->GetStringUTFChars(base_url, &isCopy);
if (strlen(convertedValue) > 0) {
config.ServiceRequestConfig.BaseUrl = convertedValue;
if (convertedValue != nullptr) {
Comment on lines 1284 to 1288
records.emplace_back(
std::to_string(id_j),
tenant_utf,
tenant_utf != nullptr ? tenant_utf : "",
latency,
persistence,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants