Summary
PIMETextService.dll terminates its TSF host process when PIME is activated while the launcher/backend RPC endpoint is unavailable.
The activation path ignores a failed callRpcMethod(). The response remains a default-null nlohmann::json, but handleRpcResponse() calls value("success", false), which throws nlohmann::json::type_error.306. The exception escapes the TSF/COM callback and terminates the host.
This was observed independently in Explorer, Task Manager, conhost, and SearchHost.
Affected build and environment
- PIME
1.3.0-beta2
- Commit
9f6a1e9161b7f609eb1fadf282048c2907da04c9
- Official CI x64
PIMETextService.dll
- PE timestamp
0x6A653E3C, checksum 0x8EC54, image size 0x82000
- Windows 11 Pro for Workstations 25H2, build
26200.8039, x64
Safe reproduction
Please use a disposable VM or isolated TSF test host rather than Explorer.
- Register the affected x64 build in the disposable environment.
- Keep
PIMELauncher or its backend unavailable so the RPC connection cannot be established.
- Activate PIME in an x64 sacrificial TSF host.
- The host terminates during
Client::onActivate().
A deterministic source-level test can make callRpcMethod() return false while leaving ret default-null, then invoke Client::onActivate().
Crash evidence
Nine independently captured LocalDumps contain the same PIME frame:
KERNELBASE!RaiseException
PIMETextService+0x360d9
Exception metadata and return addresses from the exact official CI binary resolve to:
nlohmann::json::type_error, id 306
PIMETextService+0x1bcb1 json::value() type-error throw
PIMETextService+0x2006c Client::handleRpcResponse(), reading "success"
PIMETextService+0x21b7d Client::onActivate()
Observed terminal signatures include the original C++ exception 0xe06d7363, followed in several hosts by ucrtbase.dll 0xc0000409, fast-fail subcode 7 (FAST_FAIL_FATAL_APP_EXIT).
The source path is:
Client::onActivate() creates default-null json ret.
- It calls
callRpcMethod(req, ret) but ignores the returned false.
callRpcMethod() can return false on connection, pipe, parsing, or sequence failures without producing an object response.
handleRpcResponse() calls msg.value("success", false).
- nlohmann
value() throws type error 306 when the value is not an object.
- No exception barrier prevents it from escaping the TSF/COM activation callback.
This appears to be a regression from e5cf1916, which migrated from JsonCpp:
msg.get("success", false).asBool()
to nlohmann:
msg.value("success", false)
The old expression tolerated an empty/default Json::Value; the new expression throws for a null JSON value.
The exact initial RPC failure reason cannot be recovered because no launcher log survived. Connection refusal, timeout, malformed reply, sequence mismatch, and broken pipe are all possible triggers. The fatal defect is independent of that trigger: a normal RPC failure must not escape a TSF callback or terminate its host.
Expected behavior
If the launcher, backend, pipe, or RPC response is unavailable or invalid, activation should fail cleanly. PIME should remain inactive and must not terminate the TSF host process.
Suggested fix
At minimum, honor the RPC result before inspecting the response:
json ret;
if (!callRpcMethod(req, ret) || !handleRpcResponse(ret)) {
isActivated_ = false;
return;
}
isActivated_ = true;
For defense in depth:
- Make
handleRpcResponse() reject non-object JSON before calling value().
- Validate the type of
success.
- Apply the same RPC-result check to deactivation and other callers that ignore
callRpcMethod().
- Make teardown paths non-throwing.
- Catch JSON and other C++ exceptions before they can cross any TSF/COM callback boundary.
Regression tests
- A failed
callRpcMethod() with a default-null response does not throw and leaves PIME inactive.
- Null, malformed, non-object, missing-
success, and wrong-type-success responses do not throw.
- Activating with the launcher/backend absent does not terminate an isolated TSF host.
- Losing the connection during deactivation does not let an exception escape.
- No C++ exception can cross a TSF/COM callback boundary.
The dumps are not attached because they contain host-process memory. The evidence above was extracted without including personal data.
Summary
PIMETextService.dllterminates its TSF host process when PIME is activated while the launcher/backend RPC endpoint is unavailable.The activation path ignores a failed
callRpcMethod(). The response remains a default-nullnlohmann::json, buthandleRpcResponse()callsvalue("success", false), which throwsnlohmann::json::type_error.306. The exception escapes the TSF/COM callback and terminates the host.This was observed independently in Explorer, Task Manager, conhost, and SearchHost.
Affected build and environment
1.3.0-beta29f6a1e9161b7f609eb1fadf282048c2907da04c9PIMETextService.dll0x6A653E3C, checksum0x8EC54, image size0x8200026200.8039, x64Safe reproduction
Please use a disposable VM or isolated TSF test host rather than Explorer.
PIMELauncheror its backend unavailable so the RPC connection cannot be established.Client::onActivate().A deterministic source-level test can make
callRpcMethod()returnfalsewhile leavingretdefault-null, then invokeClient::onActivate().Crash evidence
Nine independently captured LocalDumps contain the same PIME frame:
Exception metadata and return addresses from the exact official CI binary resolve to:
Observed terminal signatures include the original C++ exception
0xe06d7363, followed in several hosts byucrtbase.dll0xc0000409, fast-fail subcode 7 (FAST_FAIL_FATAL_APP_EXIT).The source path is:
Client::onActivate()creates default-nulljson ret.callRpcMethod(req, ret)but ignores the returnedfalse.callRpcMethod()can returnfalseon connection, pipe, parsing, or sequence failures without producing an object response.handleRpcResponse()callsmsg.value("success", false).value()throws type error 306 when the value is not an object.This appears to be a regression from
e5cf1916, which migrated from JsonCpp:to nlohmann:
The old expression tolerated an empty/default
Json::Value; the new expression throws for a null JSON value.The exact initial RPC failure reason cannot be recovered because no launcher log survived. Connection refusal, timeout, malformed reply, sequence mismatch, and broken pipe are all possible triggers. The fatal defect is independent of that trigger: a normal RPC failure must not escape a TSF callback or terminate its host.
Expected behavior
If the launcher, backend, pipe, or RPC response is unavailable or invalid, activation should fail cleanly. PIME should remain inactive and must not terminate the TSF host process.
Suggested fix
At minimum, honor the RPC result before inspecting the response:
For defense in depth:
handleRpcResponse()reject non-object JSON before callingvalue().success.callRpcMethod().Regression tests
callRpcMethod()with a default-null response does not throw and leaves PIME inactive.success, and wrong-type-successresponses do not throw.The dumps are not attached because they contain host-process memory. The evidence above was extracted without including personal data.