|
16 | 16 | #include <arcana/threading/blocking_concurrent_queue.h> |
17 | 17 | #include <atomic> |
18 | 18 | #include <chrono> |
| 19 | +#include <cstdint> |
19 | 20 | #include <future> |
20 | 21 | #include <iostream> |
21 | 22 | #include <thread> |
@@ -276,6 +277,78 @@ TEST(AppRuntime, DestroyDoesNotDeadlock) |
276 | 277 | testThread.join(); |
277 | 278 | } |
278 | 279 |
|
| 280 | +// The V8JSI Node-API shim does not implement napi_create_dataview / |
| 281 | +// napi_get_dataview_info (its DataView::New throws "TODO"), so this native test |
| 282 | +// only builds on the Chakra, V8, and JavaScriptCore backends. The size_t-width |
| 283 | +// guard is required because the overflow scenario below needs a 64-bit size_t. |
| 284 | +#if (SIZE_MAX > 0xFFFFFFFFu) && !defined(JSRUNTIMEHOST_NAPI_ENGINE_JSI) |
| 285 | +TEST(NodeApi, CreateDataViewRejectsOverflowingRange) |
| 286 | +{ |
| 287 | + // Regression: napi_create_dataview must reject a (byte_offset, byte_length) |
| 288 | + // pair whose sum overflows size_t. The pre-fix code performed an unchecked |
| 289 | + // `byte_offset + byte_length > bufferLength` comparison; with the inputs |
| 290 | + // below the 64-bit sum wraps to 8 and slips past it. It then truncated the |
| 291 | + // values to 32-bit (offset -> 0, length -> 8) and created a valid 8-byte |
| 292 | + // DataView, but stored the ORIGINAL 64-bit offset/length in DataViewInfo, |
| 293 | + // which napi_get_dataview_info hands back alongside the small real buffer -- |
| 294 | + // an out-of-bounds access primitive. This path is not reachable from JS |
| 295 | + // `new DataView`, so it is covered natively here. The scenario requires a |
| 296 | + // 64-bit size_t (where the 32-bit truncation diverged from the stored value), |
| 297 | + // hence the size_t-width guard. |
| 298 | + Babylon::AppRuntime runtime{}; |
| 299 | + |
| 300 | + std::promise<bool> overflowSafe; |
| 301 | + std::promise<bool> validAccepted; |
| 302 | + |
| 303 | + runtime.Dispatch([&overflowSafe, &validAccepted](Napi::Env env) { |
| 304 | + napi_env nenv{env}; |
| 305 | + |
| 306 | + Napi::ArrayBuffer arrayBuffer{Napi::ArrayBuffer::New(env, 16)}; |
| 307 | + napi_value arrayBufferValue{arrayBuffer}; |
| 308 | + |
| 309 | + // Low 32 bits are individually valid for the 16-byte buffer (offset 0, |
| 310 | + // length 8), but the full 64-bit values are enormous and their sum wraps |
| 311 | + // around size_t to 8. |
| 312 | + const size_t hugeOffset{0xFFFFFFFF00000000ull}; |
| 313 | + const size_t hugeLength{0x0000000100000008ull}; |
| 314 | + |
| 315 | + napi_value result{nullptr}; |
| 316 | + napi_status status{napi_create_dataview(nenv, hugeLength, arrayBufferValue, hugeOffset, &result)}; |
| 317 | + |
| 318 | + bool safe; |
| 319 | + if (status != napi_ok || result == nullptr) |
| 320 | + { |
| 321 | + // Fixed path: the out-of-range request is rejected outright. |
| 322 | + safe = true; |
| 323 | + } |
| 324 | + else |
| 325 | + { |
| 326 | + // If creation unexpectedly succeeds, the reported extents must still |
| 327 | + // lie within the 16-byte backing buffer (i.e. not the raw 64-bit |
| 328 | + // inputs). The pre-fix code reported the huge stored values here. |
| 329 | + size_t reportedLength{0}; |
| 330 | + size_t reportedOffset{0}; |
| 331 | + void* data{nullptr}; |
| 332 | + napi_get_dataview_info(nenv, result, &reportedLength, &data, nullptr, &reportedOffset); |
| 333 | + safe = reportedOffset <= 16 && reportedLength <= 16 && reportedOffset + reportedLength <= 16; |
| 334 | + } |
| 335 | + |
| 336 | + // Clear any pending range error so it doesn't surface as an unhandled error. |
| 337 | + napi_value pendingException{nullptr}; |
| 338 | + napi_get_and_clear_last_exception(nenv, &pendingException); |
| 339 | + overflowSafe.set_value(safe); |
| 340 | + |
| 341 | + // A legitimate offset/length pair must still succeed. |
| 342 | + napi_value validResult{nullptr}; |
| 343 | + napi_status validStatus{napi_create_dataview(nenv, 8, arrayBufferValue, 4, &validResult)}; |
| 344 | + validAccepted.set_value(validStatus == napi_ok && validResult != nullptr); |
| 345 | + }); |
| 346 | + |
| 347 | + EXPECT_TRUE(overflowSafe.get_future().get()); |
| 348 | + EXPECT_TRUE(validAccepted.get_future().get()); |
| 349 | +} |
| 350 | +#endif |
| 351 | + |
279 | 352 | int RunTests() |
280 | 353 | { |
281 | 354 | testing::InitGoogleTest(); |
|
0 commit comments