R35.6 libnvjpeg: jpegTegraEncoderMgrCreate registers event callback with a dangling stack pointer → SIGSEGV in NvOsSemaphoreSignal

Environment

  • Jetson AGX Xavier (Connect Tech AGX101 Rogue carrier)
  • L4T R35.6.5 (nvidia-l4t-core / nvidia-l4t-multimedia / nvidia-l4t-jetson-multimedia-api 35.6.5-20260701022834)
  • libnvjpeg.so md5 1442835f5142724df475ac8502d48f40
  • The same application on L4T R32.6.1 has never shown this crash.

Summary

On R35.6 we get intermittent SIGSEGVs inside hardware JPEG encoding via the stock NvJPEGEncoder::encodeFromFd() sample class. The encode is a 4096×2160 YUV420 NvBufSurface at quality 99, run at about 1 Hz from one thread of a multi-threaded process.

Minidumps and disassembly point to a bug in libnvjpeg itself:

  1. jpegTegraEncoderMgrCreate registers the NvMM block’s event callback with the address of one of its own stack locals as the client context, not the manager object.
  2. The callback treats that context as the manager object.
  3. On event type 1, it signals two semaphores it reads at ctx+2328 and ctx+2336. Once jpegTegraEncoderMgrCreate has returned, ctx points into a dead stack frame. Those “semaphores” are whatever happens to be on the stack, and NvOsSemaphoreSignal() → pthread_mutex_lock() faults.

Event type 1 appears to be the block’s error notification. libnvmmlite_image raises it when the hardware operation started by command 1 returns a status other than 0 or 14. In both of our crash dumps that status was 4. So the sequence was: a hardware JPEG operation failed, and the error-reporting path crashed instead of returning the error.

Disassembly (libnvjpeg.so, R35.6.5)

Registration in jpegTegraEncoderMgrCreate:

3c14c: f9402fe0  ldr  x0, [sp, #88]        // x0 = mgr (local variable)
3c150: b00000e2  adrp x2, 0x59000
3c154: 910163e1  add  x1, sp, #88          // x1 = &mgr  <-- address of the stack local
3c158: f947b842  ldr  x2, [x2, #3952]      // x2 = callback (libnvjpeg+0x3f110, via R_AARCH64_RELATIVE at 0x59f70)
3c15c: f9447800  ldr  x0, [x0, #2288]      // x0 = mgr->block
3c160: f9401403  ldr  x3, [x0, #40]
3c164: d63f0060  blr  x3                   // block->SetEventCallback(block, &mgr, callback)

The callback at libnvjpeg+0x3f110:

3f110: 7100043f  cmp  w1, #1               // only event type 1 does anything
3f114: 54000060  b.eq 0x3f120
3f118: 52800000  mov  w0, #0
3f11c: d65f03c0  ret
3f120: a9bf7bf3  stp  x19, x30, [sp, #-16]!
3f124: aa0003f3  mov  x19, x0              // x0 = client context, used as if it were mgr
3f128: f9448c00  ldr  x0, [x0, #2328]
3f12c: 97ff1715  bl   NvOsSemaphoreSignal
3f130: f9449260  ldr  x0, [x19, #2336]
3f134: 97ff1713  bl   NvOsSemaphoreSignal

The callback is invoked synchronously from jpeg_write_raw_data → jpegTegraEncoderCompress (+0x3f7c8, block->vtbl[1](block, 1, ...)) → libnvmmlite → libnvmmlite_utils → libnvmmlite_image → callback.

In libnvmmlite_image (+0x4604..+0x46a4), command 1 runs the operation under a mutex. If the returned status is neither 0 nor 14, it calls the client callback with event 1 and a 24-byte payload {size=24, version=1, ..., status}:

4670: 7100027f  cmp  w19, #0
4674: 7a4e1a64  ccmp w19, #14, #4, ne       // status == 0 or status == 14 -> return normally
4678: 54fffd80  b.eq 0x4628
467c: f9401ae0  ldr  x0, [x23, #48]         // client context (the dangling &mgr)
4680: d2800301  mov  x1, #24
4684: f94036e4  ldr  x4, [x23, #104]        // client callback (libnvjpeg+0x3f110)
4688: f2c00021  movk x1, #1, lsl #32
468c: f9001be1  str  x1, [sp, #48]          // payload {size=24, version=1}
4694: b9003ff3  str  w19, [sp, #60]         // payload status
4698: 2a1603e1  mov  w1, w22                // event = 1
46a0: d63f0080  blr  x4

Evidence from the field crash (crashpad minidump)

Crash reason:  SIGSEGV / SEGV_MAPERR
Crash address: 0x000000000000a99f

Thread "tile encoder" (crashed)
 0  libpthread.so.0 + 0x9c00        pthread_mutex_lock, x0 = 0xa98f
 1  libnvos.so + 0xa8c0             NvOsSemaphoreSignal(0xa98f)
 2  libnvjpeg.so + 0x3f130          event callback (return address after the first signal)
 ...
    NvJPEGEncoder::encodeFromFd     (libnvmm_common, built from samples/common/classes/NvJpegEncoder.cpp)
  • The callback’s context was 0xfffd65ff0ae8, an address on the crashing thread’s own stack.
  • ctx+2328 held 0xa98f and ctx+2336 held another stack address, so neither is a semaphore.
  • The event payload was still on the stack: status = 4 in both dumps we have (two different units).
  • All other threads were idle in condition-variable waits. Nothing else was inside libnvjpeg, NVENC or NvBufSurfTransform at the time.
  • Other occurrences faulted at 0x9f0 and 0x9c8d, which are also garbage “semaphore” pointers.

Confirmed with gdb on a bench unit (same L4T)

  • A breakpoint on the registration call shows x1 = sp+88, a stack address. *(x1) is the heap-allocated manager.
  • A breakpoint on the callback’s event-1 path never fired in our bench runs (hundreds of normal encodes). So the bad pointer is harmless until a hardware operation fails, and we cannot yet make it fail on demand. In the field it failed after about 50 minutes, with the full camera, NVENC, VIC and CUDA pipeline running, on several units within about a minute of each other.
  • A gdb breakpoint on the block->vtbl[1](block, 1, ...) call in jpegTegraEncoderCompress (+0x3f7c8) wedged the first encode. That point seems to be timing-sensitive.

R32.6.1’s libnvjpeg contains no calls to NvOsSemaphoreSignal at all, so this appears to be a new R35 code path.

Questions

  1. Can you confirm the jpegTegraEncoderMgrCreate callback registration bug, and is a fix planned for R35? The client data should presumably be mgr, not &mgr on the stack.
  2. What does operation status 4 mean for the JPEG encode block, and what can cause it on a 4096×2160 YUV420 pitch-linear NvBufSurface that encodes fine thousands of times beforehand? Knowing that would let us avoid the condition until there is a fix.

Disclaimer: The following analysis is based on review of the provided disassembly and minidump logs for peer troubleshooting and verification.

@chris929 Excellent disassembly and minidump analysis. You’ve accurately isolated the root cause: passing &mgr (sp+88) instead of mgr to SetEventCallback leaves a dangling stack pointer that only detonates when the hardware error path (event = 1) executes and attempts to signal semaphores from ctx+2328 / ctx+2336.

Regarding your questions on status 4 and potential mitigations while waiting for a binary patch in L4T:

  • Status 4 Origin & Carrier Board Context: Running on a third-party carrier like the Connect Tech AGX101 Rogue means you are utilizing customized CTI BSP layers and device trees. Under heavy, sustained multi-threaded loads (Camera + NVENC + VIC + CUDA), subtle power rail or clock scaling nuances on custom carriers can occasionally surface hardware timeouts or command queue stalls in libnvmmlite_image, returning error status 4.

  • Workarounds / Mitigations:

    1. Pitch/Stride Alignment: Ensure NvBufSurface pitch alignment strictly satisfies hardware requirements (e.g., 256/512-byte boundaries) for 4K frames to prevent intermittent hardware engine rejections.

    2. Encoding Concurrency Throttling: Wrapping NvJPEGEncoder::encodeFromFd() calls in a lightweight mutex or rate-limiter can help avoid triggering hardware timeouts in libnvmmlite_image under high system pressure.

    3. Error Isolation: If possible, move the JPEG encoding path into a dedicated subprocess so an unhandled SIGSEGV in libnvjpeg won’t bring down your primary pipeline until NVIDIA issues a fix.

Hi,
This may be a known issue. Please apply the prebuilt lib and try:

Making sure you're not a bot!
Multimedia: [MMAPI] using-jpeg-encode-a-memory-leak-was-discovered

Thanks DaneLLL. I tried the prebuilt library but I’m getting the same crash (same call stack, same frequency and symptoms; I verified that the patched library is being loaded). The crash is apparently happening when there’s a failure, and the failure path is where the bad pointer is being accessed. That’s unchanged in the patched library.

Disclosure: This technical analysis was synthesized using AI assistance to review the referenced L4T patch threads, minidump analysis, and binary execution paths for peer troubleshooting.

@chris929 Looking closely at thread #338946, that patch targets a completely separate memory management issue:

  • Thread #338946 Patch: Addresses a memory leak / RSS growth issue during continuous jpeg_encode execution where buffer allocations are not freed cleanly.

  • Your Bug (#386414): A stack-use-after-scope binary flaw inside jpegTegraEncoderMgrCreate. Passing the address of a stack-local variable (&mgr via sp+88) as the client context rather than the heap object pointer leaves a dangling pointer.

Because the bug lives in the error-reporting callback path of libnvjpeg.so, no amount of memory leak patching will prevent SIGSEGV when libnvmmlite_image hits a hardware error status 4 and attempts to signal semaphores from invalid stack addresses. An out-of-band update for libnvjpeg.so from NVIDIA engineering remains necessary.

Hi,
Please try to reproduce it on AGX Xavier developer kit and share us the steps. We will set up developer kit and check.

We have the sample:

/usr/src/jetson_multimedia_api/samples/05_jpeg_encode/

May share us a patch to the sample and the commands/steps.

Disclosure: This technical analysis was synthesized using AI assistance to review the provided L4T multimedia API code paths and assembly disassembly for peer troubleshooting.

For the NVIDIA team to reproduce this issue using /usr/src/jetson_multimedia_api/samples/05_jpeg_encode/, the key challenge is that the crash is conditional:

  1. Why standard 05_jpeg_encode won’t crash immediately: Under normal conditions, 05_jpeg_encode completes successfully because the hardware returns status 0 or 14. The dangling stack pointer registered in jpegTegraEncoderMgrCreate (block->SetEventCallback(block, &mgr, callback)) sits dormant during normal operation.

  2. How to reproduce the failure path: The crash only triggers when libnvmmlite_image receives an error status (such as status 4 under high system/memory contention) and attempts to invoke event callback 1.

  3. Static Verification Alternative: Rather than waiting for an intermittent hardware timeout under heavy multi-threaded stress (VIC + NVENC + CUDA), NVIDIA internal engineering can verify the bug directly in libnvjpeg.so (R35.6.5) via disassembly at offset 0x3c154:

    3c14c: f9402fe0  ldr x0, [sp, #88]   // x0 = mgr
    3c154: 910163e1  add x1, sp, #88     // x1 = &mgr (passing address of stack variable instead of x0/mgr)
    3c164: d63f0060  blr x3              // block->SetEventCallback(block, &mgr, callback)
    
    

    When callback (at 0x3f110) runs on event 1, it expects x0 to be mgr, but reads the dangling stack address instead, invoking NvOsSemaphoreSignal() on dead stack memory (ctx+2328).

I was unsuccessful in triggering the failure in a variety of stress tests already–it requires an encoding failure to trigger, and we’re seeing it consistently with our full stack running (stereo camera capture, hardware video encoding, multiple JPEG encoding streams, extensive GPU use via TensortRT and CUDA kernels, and another model on the DLA as well). It happens once every minute or so on our live system (and hits any jpeg encoder thread) but I could not reproduce it with standalone code (even running multiple encoders with varying sizes).

Hi,
Or please check if you can share an built binary we can run on AGX Xavier developer kit to reproduce the issue.