CVE-2019-19624 (C++, published 2019-12-06T15:15Z)
Description: 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.
Severity: MEDIUM (CVSS v3 base score: 6.5)
Weakness: CWE-125 \u2014 Out-of-bounds Read
The product reads data past the end, or before the beginning, of the intended buffer.
Repository: https://github.com/opencv/opencv
Files changed:
modules/video/src/dis_flow.cpp
modules/video/test/test_OF_accuracy.cpp
Commit message: video:fixed DISOpticalFlow segfault from small img


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);
}