Skip to content

fix: rotate/flip segfault from AV_CEIL_RSHIFT on unsigned dimensions#4900

Merged
connortechnology merged 1 commit into
ZoneMinder:masterfrom
SteveGilvarry:fix/rotate-plane-segfault
Jun 12, 2026
Merged

fix: rotate/flip segfault from AV_CEIL_RSHIFT on unsigned dimensions#4900
connortechnology merged 1 commit into
ZoneMinder:masterfrom
SteveGilvarry:fix/rotate-plane-segfault

Conversation

@SteveGilvarry

Copy link
Copy Markdown
Member

What

Image::Rotate and Image::Flip (rewritten in the PR #4788 series) compute chroma plane dimensions with AV_CEIL_RSHIFT(width, log2_chroma_w). The macro's runtime form is -((-(a)) >> (b)) — a branchless ceiling that relies on arithmetic right shift of a negative value, valid only for signed operands (FFmpeg always passes int). Image::width/height are unsigned int, so the negation wraps, the shift is logical, and the result is 2^31 + ceil(a/2^b) instead of ceil(a/2^b).

Captured in gdb on a live 1280x720 ROTATE_270 monitor:

rotate_plane (src=..., src_linesize=640,
              src_w=2147484288,   // 2^31 + 640
              src_h=2147484008,   // 2^31 + 360
              dst=..., dst_linesize=384, bpp=1, angle=270)

The chroma loops then index ~2 billion rows out of bounds → immediate SIGSEGV.

Effect

zmc's decoder thread segfaults on the first decoded frame of any monitor with a rotated/flipped orientation and a planar pixel format. A rotated monitor with Decoding: Always crash-loops; Ondemand crashes as soon as anything views it.

Fix

Replace the macro at both sites (Rotate, Flip) with an explicit unsigned-safe ceiling shift helper, with a comment documenting why AV_CEIL_RSHIFT must not be used on unsigned operands.

Tests

New tests/zm_image.cpp: Rotate 90/180/270 and Flip on YUV420P with per-plane marker-pixel mapping checks, plus odd dimensions (101x57) exercising the chroma ceiling.

  • Before the fix: the Rotate cases segfault (exit 139) — verified by stashing the fix.
  • After: all pass; full suite green via ctest.
  • Live verification: 1280x720 ROTATE_270 monitor with Decoding: Always — unpatched zmc crashes within seconds of decode start, patched zmc ran a clean 90s session under gdb (continuous decode + rotate).

🤖 Generated with Claude Code

Image::Rotate and Image::Flip computed chroma plane dimensions with
AV_CEIL_RSHIFT(width, log2_chroma_w). The macro's runtime form is
-((-(a)) >> (b)), which relies on arithmetic right shift of a negative
value and is only valid for signed operands - FFmpeg always passes int.
Image::width/height are unsigned, so the negation wraps, the shift is
logical, and the result is 2^31 + ceil(w/2^b) instead of ceil(w/2^b):
for 1280x720 the chroma rotate received src_w=2147484288 and
src_h=2147484008 (captured in gdb), writing gigabytes out of bounds.

Effect: zmc's decoder thread segfaulted on the first decoded frame of
any monitor with a rotated or flipped orientation and a planar pixel
format - a monitor with decoding Always crash-loops.

Replace the macro at both sites with an explicit unsigned ceiling
shift helper and a comment documenting the trap.

Tests: new tests/zm_image.cpp covers Rotate 90/180/270 and Flip on
YUV420P with per-plane marker pixels, plus odd dimensions exercising
the chroma ceiling. The Rotate cases segfault before this fix (verified,
exit 139) and pass after. Full suite 85/85 via ctest. Live-verified on
a 1280x720 ROTATE_270 monitor with decoding Always: pre-fix crash
within seconds, post-fix 90s clean run under gdb.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 11, 2026 09:43

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

Fixes a crash in Image::Rotate / Image::Flip for planar pixel formats by replacing AV_CEIL_RSHIFT usage on unsigned dimensions (which can produce enormous chroma plane sizes due to unsigned wrap + logical right shift), and adds regression tests to validate per-plane mapping and odd-dimension chroma sizing.

Changes:

  • Replace AV_CEIL_RSHIFT(width/height, log2_chroma_*) with an unsigned-safe ceil_rshift() helper in Image::Rotate and Image::Flip.
  • Add Catch2 regression tests covering Rotate 90/180/270 and Flip on YUV420P, including odd luma dimensions to exercise chroma ceiling behavior.
  • Register the new test file in the tests CMake source list.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/zm_image.cpp Introduces ceil_rshift() and uses it for planar chroma plane dimension calculation in Rotate/Flip to prevent OOB/segfault.
tests/zm_image.cpp Adds regression tests validating Rotate/Flip correctness for YUV420P, including odd dimension chroma ceiling cases.
tests/CMakeLists.txt Adds zm_image.cpp to the test build.

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

Comment thread src/zm_image.cpp
Comment on lines +3124 to +3128
// Ceiling-divide an unsigned dimension by 2^shift. AV_CEIL_RSHIFT must NOT
// be used here: its runtime form is -((-(a)) >> (b)), which relies on
// arithmetic shift of a negative value and silently produces 2^31 + a/2^b
// when `a` is unsigned (logical shift), sending plane loops billions of
// samples out of bounds.
@connortechnology

Copy link
Copy Markdown
Member

I have claude working on similar issues. This is going to be a mess. Thing is: None of these things can be negative. That has to be caught much earlier.

@connortechnology

Copy link
Copy Markdown
Member

THe thing is... standard idiom is to just be signed even though a signed value should never happen.. I might disagree, but who am I. I defer to ffmpeg.

@connortechnology

Copy link
Copy Markdown
Member

Also We have a real crash here as a result of this code. Yet I feel that we are in slop land. We are going to have to engage our brains to help the AI out.

@SteveGilvarry

Copy link
Copy Markdown
Member Author

OK definitely was not working for my monitors on dev master before this, can you reproduce that? And worked after it, so I went with sounds good. I will engage mind to check what it did.

@connortechnology connortechnology merged commit 3eb0792 into ZoneMinder:master Jun 12, 2026
5 checks passed
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