Merge pull request #29853 from Thebinary110:4x-fix-dis-opticalflow-overflow

video: fix DISOpticalFlow heap-buffer-overflow with patch_size > border_size - #29853

Fixes #20185. 4.x companion to #29715.

@asmorkalov asked to retarget #29715 to 4.x since the issue reproduces there too, but that PR's branch descends from 5.x, so literally changing its base produces an unreviewable ~2M-line diff (the two branches have diverged far beyond this module). Opening a separate PR instead: same two commits, cherry-picked cleanly onto 4.x's current tip with zero conflicts (`modules/video/src/dis_flow.cpp` is byte-for-byte identical between the branches apart from this fix).

### What / why
See #29715 for the full writeup. Summary: `DISOpticalFlowImpl` pads `I1` with a fixed 16px border, while `PatchInverseSearch`'s search-position clamp lets a patch be placed up to `patch_size - 1` px outside the image -- safe only while `patch_size <= border_size`. Since `patch_size` is user-settable with no upper bound relative to the hardcoded border, `setPatchSize()` past 16 (or a large-enough temporal-candidate flow) reads past the end of the padded buffer (confirmed via AddressSanitizer).

Fix (per review on #29715): rather than growing the persistent `border_size` object field to match `patch_size` (which a reviewer correctly flagged as the wrong place for a per-call derived quantity), the search clamp itself (`i/j_lower_limit`, `i/j_upper_limit` in `PatchInverseSearch_ParBody`) now accounts for the read window needing to stay inside the *existing* padded buffer. For `patch_size <= border_size` (every built-in preset) the bounds are algebraically identical to the originals -- `border_size` itself is untouched.

### Testing
Verified this reproduces identically on 4.x: built an ASan-instrumented Debug configuration (`core+imgproc+imgcodecs+features2d+flann+video+ts`), confirmed the crash reproduces on pristine 4.x (same crash site as the original report), confirmed it's gone with this fix, and ran the new `regression_20185_patch_larger_than_border` / `regression_20185_stress` tests (5 repeated runs) plus the full `opencv_test_video` suite -- no failures attributable to this change (everything else failing needs `opencv_extra` test data not configured in this scoped build).

### PR checklist
- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on code under GPL or another incompatible license.
- [x] The PR is proposed to the proper branch (4.x, per maintainer request on #29715).
- [x] Accuracy tests included (see above).
- [x] No public API/behavior change, so no documentation or sample updates needed.
This commit is contained in:
CodeCraftsman
2026-09-08 00:13:55 +05:30
committed by GitHub
parent 415ba6a444
commit 0c6b4a9b6d
2 changed files with 27 additions and 5 deletions

View File

@@ -818,10 +818,12 @@ void DISOpticalFlowImpl::PatchInverseSearch_ParBody::operator()(const Range &ran
int i, j, dir;
int start_is, end_is, start_js, end_js;
int start_i, start_j;
float i_lower_limit = bsz - psz + 1.0f;
float i_upper_limit = bsz + dis->h - 1.0f;
float j_lower_limit = bsz - psz + 1.0f;
float j_upper_limit = bsz + dis->w - 1.0f;
// Clamp so the psz+1 (bilinear) read stays inside the padded I1_ext buffer even when
// psz > bsz; unchanged for psz <= bsz (the common case). See #20185.
float i_lower_limit = std::max(bsz - psz + 1.0f, 0.0f);
float i_upper_limit = std::min(bsz + dis->h - 1.0f, dis->h + 2.0f * bsz - 1.0f - psz);
float j_lower_limit = std::max(bsz - psz + 1.0f, 0.0f);
float j_upper_limit = std::min(bsz + dis->w - 1.0f, dis->w + 2.0f * bsz - 1.0f - psz);
float dUx, dUy, i_I1, j_I1, w00, w01, w10, w11, dx, dy;
#define INIT_BILINEAR_WEIGHTS(Ux, Uy) \

View File

@@ -197,4 +197,24 @@ TEST(DenseOpticalFlow_DIS, ManualCoarsestScale)
EXPECT_EQ(dis->getCoarsestScale(), -1);
}
}} // namespace
// See https://github.com/opencv/opencv/issues/20185
TEST(DenseOpticalFlow_DIS, regression_20185_patch_larger_than_border)
{
Mat prev(240, 320, CV_8UC1), next(240, 320, CV_8UC1);
theRNG().fill(prev, RNG::UNIFORM, 0, 256);
theRNG().fill(next, RNG::UNIFORM, 0, 256);
Ptr<DISOpticalFlow> dis = DISOpticalFlow::create(DISOpticalFlow::PRESET_MEDIUM);
dis->setPatchStride(10);
Mat flow(prev.size(), CV_32FC2, Scalar(60.f, 60.f));
ASSERT_NO_THROW(dis->calc(prev, next, flow));
EXPECT_EQ(flow.size(), prev.size());
dis->setPatchSize(25);
flow.setTo(Scalar(60.f, 60.f));
ASSERT_NO_THROW(dis->calc(prev, next, flow));
EXPECT_EQ(flow.size(), prev.size());
}
}} // namespace