CVE-2019-19624: Out-of-bounds Read (CWE-125)
value: An out-of-bounds read was discovered in OpenCV before 4.1.1. Specifically, variable coarsest_scale is assumed to be greater than or equal to finest_scale within the calc()/ocl_calc() functions in dis_flow.cpp. However, this is not true when dealing with small images, leading to an out-of-bounds read of the heap-allocated arrays Ux and Uy.
CWE description: The product reads data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 6.5
Published: 2019-12-06T15:15Z
Repository: https://github.com/opencv/opencv
Language: C++
Files changed: modules/video/src/dis_flow.cpp, modules/video/test/test_OF_accuracy.cpp
Commit date: 2019-05-27 08:18:26 +0200
Commit message: video:fixed DISOpticalFlow segfault from small img

Diff: diff --git a/modules/video/src/dis_flow.cpp b/modules/video/src/dis_flow.cpp
index a453d8b2b..806d6f1dd 100644
--- a/modules/video/src/dis_flow.cpp
+++ b/modules/video/src/dis_flow.cpp
@@ -138,10 +138,12 @@ class DISOpticalFlowImpl CV_FINAL : public DISOpticalFlow
 
   private: //!< private methods and parallel sections
     void prepareBuffers(Mat &I0, Mat &I1, Mat &flow, bool use_flow);
     void precomputeStructureTensor(Mat &dst_I0xx, Mat &dst_I0yy, Mat &dst_I0xy, Mat &dst_I0x, Mat &dst_I0y, Mat &I0x,
                                    Mat &I0y);
+    int autoSelectCoarsestScale(int img_width);
+    void autoSelectPatchSizeAndScales(int img_width);
 
     struct PatchInverseSearch_ParBody : public ParallelLoopBody
     {
         DISOpticalFlowImpl *dis;
         int nstripes, stripe_sz;
@@ -433,10 +435,48 @@ void DISOpticalFlowImpl::precomputeStructureTensor(Mat &dst_I0xx, Mat &dst_I0yy,
             is++;
         }
     }
 }
 
+int DISOpticalFlowImpl::autoSelectCoarsestScale(int img_width)
+{
+    const int fratio = 5;
+    return std::max(0, (int)std::floor(log2((2.0f*(float)img_width) / ((float)fratio * (float)patch_size))));
+}
+
+void DISOpticalFlowImpl::autoSelectPatchSizeAndScales(int img_width)
+{
+    switch (finest_scale)
+    {
+    case 1:
+        patch_size = 8;
+        coarsest_scale = autoSelectCoarsestScale(img_width);
+        finest_scale = std::max(coarsest_scale-2, 0);
+        break;
+
+    case 3:
+        patch_size = 12;
+        coarsest_scale = autoSelectCoarsestScale(img_width);
+        finest_scale = std::max(coarsest_scale-4, 0);
+        break;
+
+    case 4:
+        patch_size = 12;
+        coarsest_scale = autoSelectCoarsestScale(img_width);
+        finest_scale = std::max(coarsest_scale-5, 0);
+        break;
+
+    // default case, fall-through.
+    case 2:
+    default:
+        patch_size = 8;
+        coarsest_scale = autoSelectCoarsestScale(img_width);
+        finest_scale = std::max(coarsest_scale-2, 0);
+        break;
+    }
+}
+
 DISOpticalFlowImpl::PatchInverseSearch_ParBody::PatchInverseSearch_ParBody(DISOpticalFlowImpl &_dis, int _nstripes,
                                                                            int _hs, Mat &dst_Sx, Mat &dst_Sy,
                                                                            Mat &src_Ux, Mat &src_Uy, Mat &_I0, Mat &_I1,
                                                                            Mat &_I0x, Mat &_I0y, int _num_iter,
                                                                            int _pyr_level)
@@ -1316,13 +1356,24 @@ bool DISOpticalFlowImpl::ocl_calc(InputArray I0, InputArray I1, InputOutputArray
     if (flow.sameSize(I0) && flow.depth() == CV_32F && flow.channels() == 2)
         use_input_flow = true;
     else
         flow.create(I1Mat.size(), CV_32FC2);
     UMat &u_flowMat = flow.getUMatRef();
-    coarsest_scale = min((int)(log(max(I0Mat.cols, I0Mat.rows) / (4.0 * patch_size)) / log(2.0) + 0.5), /* Original code serach for maximal movement of width/4 */
+    coarsest_scale = min((int)(log(max(I0Mat.cols, I0Mat.rows) / (4.0 * patch_size)) / log(2.0) + 0.5), /* Original code search for maximal movement of width/4 */
                          (int)(log(min(I0Mat.cols, I0Mat.rows) / patch_size) / log(2.0)));              /* Deepest pyramid level greater or equal than patch*/
 
+    if (coarsest_scale<0)
+        CV_Error(cv::Error::StsBadSize, "The input image must have either width or height >= 12");
+
+    if (coarsest_scale<finest_scale)
+    {
+        // choose the finest level based on coarsest level.
+        // Refs: https://github.com/tikroeger/OF_DIS/blob/2c9f2a674f3128d3a41c10e41cc9f3a35bb1b523/run_dense.cpp#L239
+        int original_img_width = I0.size().width;
+        autoSelectPatchSizeAndScales(original_img_width);
+    }
+
     ocl_prepareBuffers(I0Mat, I1Mat, u_flowMat, use_input_flow);
     u_Ux[coarsest_scale].setTo(0.0f);
     u_Uy[coarsest_scale].setTo(0.0f);
 
     for (int i = coarsest_scale; i >= finest_scale; i--)
@@ -1383,12 +1434,24 @@ void DISOpticalFlowImpl::calc(InputArray I0, InputArray I1, InputOutputArray flo
     if (flow.sameSize(I0) && flow.depth() == CV_32F && flow.channels() == 2)
         use_input_flow = true;
     else
         flow.create(I1Mat.size(), CV_32FC2);
     Mat flowMat = flow.getMat();
-    coarsest_scale = min((int)(log(max(I0Mat.cols, I0Mat.rows) / (4.0 * patch_size)) / log(2.0) + 0.5), /* Original code serach for maximal movement of width/4 */
+    coarsest_scale = min((int)(log(max(I0Mat.cols, I0Mat.rows) / (4.0 * patch_size)) / log(2.0) + 0.5), /* Original code search for maximal movement of width/4 */
                          (int)(log(min(I0Mat.cols, I0Mat.rows) / patch_size) / log(2.0)));              /* Deepest pyramid level greater or equal than patch*/
+
+    if (coarsest_scale<0)
+        CV_Error(cv::Error::StsBadSize, "The input image must have either width or height >= 12");
+
+    if (coarsest_scale<finest_scale)
+    {
+        // choose the finest level based on coarsest level.
+        // Refs: https://github.com/tikroeger/OF_DIS/blob/2c9f2a674f3128d3a41c10e41cc9f3a35bb1b523/run_dense.cpp#L239
+        int original_img_width = I0.size().width;
+        autoSelectPatchSizeAndScales(original_img_width);
+    }
+
     int num_stripes = getNumThreads();
 
     prepareBuffers(I0Mat, I1Mat, flowMat, use_input_flow);
     Ux[coarsest_scale].setTo(0.0f);
     Uy[coarsest_scale].setTo(0.0f);
diff --git a/modules/video/test/test_OF_accuracy.cpp b/modules/video/test/test_OF_accuracy.cpp
index affbab658..b99ffce2a 100644
--- a/modules/video/test/test_OF_accuracy.cpp
+++ b/modules/video/test/test_OF_accuracy.cpp
@@ -119,10 +119,38 @@ TEST(DenseOpticalFlow_DIS, ReferenceAccuracy)
         ASSERT_EQ(GT.cols, flow.cols);
         EXPECT_LE(calcRMSE(GT, flow), target_RMSE[i]);
     }
 }
 
+TEST(DenseOpticalFlow_DIS, InvalidImgSize_CoarsestLevelLessThanZero)
+{
+    cv::Ptr<cv::DISOpticalFlow> of = cv::DISOpticalFlow::create();
+    const int mat_size = 10;
+
+    cv::Mat x(mat_size, mat_size, CV_8UC1, 42);
+    cv::Mat y(mat_size, mat_size, CV_8UC1, 42);
+    cv::Mat flow;
+
+    ASSERT_THROW(of->calc(x, y, flow), cv::Exception);
+}
+
+// make sure that autoSelectPatchSizeAndScales() works properly.
+TEST(DenseOpticalFlow_DIS, InvalidImgSize_CoarsestLevelLessThanFinestLevel)
+{
+    cv::Ptr<cv::DISOpticalFlow> of = cv::DISOpticalFlow::create();
+    const int mat_size = 80;
+
+    cv::Mat x(mat_size, mat_size, CV_8UC1, 42);
+    cv::Mat y(mat_size, mat_size, CV_8UC1, 42);
+    cv::Mat flow;
+
+    of->calc(x, y, flow);
+
+    ASSERT_EQ(flow.rows, mat_size);
+    ASSERT_EQ(flow.cols, mat_size);
+}
+
 TEST(DenseOpticalFlow_VariationalRefinement, ReferenceAccuracy)
 {
     Mat frame1, frame2, GT;
     ASSERT_TRUE(readRubberWhale(frame1, frame2, GT));
     float target_RMSE = 0.86f;

Vulnerable code:     coarsest_scale = min((int)(log(max(I0Mat.cols, I0Mat.rows) / (4.0 * patch_size)) / log(2.0) + 0.5), /* Original code serach for maximal movement of width/4 */
    coarsest_scale = min((int)(log(max(I0Mat.cols, I0Mat.rows) / (4.0 * patch_size)) / log(2.0) + 0.5), /* Original code serach for maximal movement of width/4 */
Fixed code:     int autoSelectCoarsestScale(int img_width);
    void autoSelectPatchSizeAndScales(int img_width);
int DISOpticalFlowImpl::autoSelectCoarsestScale(int img_width)
{
    const int fratio = 5;
    return std::max(0, (int)std::floor(log2((2.0f*(float)img_width) / ((float)fratio * (float)patch_size))));
}

void DISOpticalFlowImpl::autoSelectPatchSizeAndScales(int img_width)
{
    switch (finest_scale)
    {
    case 1:
        patch_size = 8;
        coarsest_scale = autoSelectCoarsestScale(img_width);
        finest_scale = std::max(coarsest_scale-2, 0);
        break;

    case 3:
        patch_size = 12;
        coarsest_scale = autoSelectCoarsestScale(img_width);
        finest_scale = std::max(coarsest_scale-4, 0);
        break;

    case 4:
        patch_size = 12;
        coarsest_scale = autoSelectCoarsestScale(img_width);
        finest_scale = std::max(coarsest_scale-5, 0);
        break;

    // default case, fall-through.
    case 2:
    default:
        patch_size = 8;
        coarsest_scale = autoSelectCoarsestScale(img_width);
        finest_scale = std::max(coarsest_scale-2, 0);
        break;
    }
}

    coarsest_scale = min((int)(log(max(I0Mat.cols, I0Mat.rows) / (4.0 * patch_size)) / log(2.0) + 0.5), /* Original code search for maximal movement of width/4 */
    if (coarsest_scale<0)
        CV_Error(cv::Error::StsBadSize, "The input image must have either width or height >= 12");

    if (coarsest_scale<finest_scale)
    {
        // choose the finest level based on coarsest level.
        // Refs: https://github.com/tikroeger/OF_DIS/blob/2c9f2a674f3128d3a41c10e41cc9f3a35bb1b523/run_dense.cpp#L239
        int original_img_width = I0.size().width;
        autoSelectPatchSizeAndScales(original_img_width);
    }

    coarsest_scale = min((int)(log(max(I0Mat.cols, I0Mat.rows) / (4.0 * patch_size)) / log(2.0) + 0.5), /* Original code search for maximal movement of width/4 */

    if (coarsest_scale<0)
        CV_Error(cv::Error::StsBadSize, "The input image must have either width or height >= 12");

    if (coarsest_scale<finest_scale)
    {
        // choose the finest level based on coarsest level.
        // Refs: https://github.com/tikroeger/OF_DIS/blob/2c9f2a674f3128d3a41c10e41cc9f3a35bb1b523/run_dense.cpp#L239
        int original_img_width = I0.size().width;
        autoSelectPatchSizeAndScales(original_img_width);
    }

TEST(DenseOpticalFlow_DIS, InvalidImgSize_CoarsestLevelLessThanZero)
{
    cv::Ptr<cv::DISOpticalFlow> of = cv::DISOpticalFlow::create();
    const int mat_size = 10;

    cv::Mat x(mat_size, mat_size, CV_8UC1, 42);
    cv::Mat y(mat_size, mat_size, CV_8UC1, 42);
    cv::Mat flow;

    ASSERT_THROW(of->calc(x, y, flow), cv::Exception);
}

// make sure that autoSelectPatchSizeAndScales() works properly.
TEST(DenseOpticalFlow_DIS, InvalidImgSize_CoarsestLevelLessThanFinestLevel)
{
    cv::Ptr<cv::DISOpticalFlow> of = cv::DISOpticalFlow::create();
    const int mat_size = 80;

    cv::Mat x(mat_size, mat_size, CV_8UC1, 42);
    cv::Mat y(mat_size, mat_size, CV_8UC1, 42);
    cv::Mat flow;

    of->calc(x, y, flow);

    ASSERT_EQ(flow.rows, mat_size);
    ASSERT_EQ(flow.cols, mat_size);
}