Skip to content

Fix out-of-bounds access in setSize() from cv::Mat() constructor#18473

Merged
opencv-pushbot merged 1 commit intoopencv:3.4from
BioDataAnalysis:bda_fix_cv_mat_steps
Oct 5, 2020
Merged

Fix out-of-bounds access in setSize() from cv::Mat() constructor#18473
opencv-pushbot merged 1 commit intoopencv:3.4from
BioDataAnalysis:bda_fix_cv_mat_steps

Conversation

@emmenlau
Copy link
Copy Markdown
Contributor

Pull Request Readiness Checklist

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on a code under GPL or other license that is incompatible with OpenCV
  • The PR is proposed to proper branch
  • There is reference to original bug report and related work
  • There is accuracy test, performance test and test data in opencv_extra repository, if applicable
    Patch to opencv_extra has the same branch name.
  • The feature is well documented and sample code can be built with the project CMake

This PR fixes an issue in the setSize() method of cv::Mat. According to the documentation (for example https://docs.opencv.org/4.4.0/d3/d63/classcv_1_1Mat.html#a5fafc033e089143062fd31015b5d0f40) the parameter const size_t* steps is an Array of ndims-1 steps in case of a multi-dimensional array (the last step is always set to the element size). This is reflected in the implementation, where the m.step.p[i] is always set to esz for the last dimension:

m.step.p[i] = i < _dims-1 ? _steps[i] : esz;

However a few lines above, there is a check if _steps[i] is a multiple of esz1. This statement does access the ith element of _steps for the last dimension. This is an out of bounds access according to the documentation:

if (_steps[i] % esz1 != 0)

Proposed patch fixes the out-of-bounds access and restores the documented behavior.

@alalek
Copy link
Copy Markdown
Member

alalek commented Oct 1, 2020

Please provide a reproducer (simple test is preferable).

@emmenlau
Copy link
Copy Markdown
Contributor Author

emmenlau commented Oct 1, 2020

Dear @alalek thanks for your consideration. Since the problem is an out-of-bounds access via pointer, it may not actually show an error. It will be easier to understand if you just look into the code. For me it crashed in maybe 1 out of 100 cases.

To reproduce, just use the constructor from this link: https://docs.opencv.org/4.4.0/d3/d63/classcv_1_1Mat.html#a5fafc033e089143062fd31015b5d0f40

Example:

std::vector<int> sizes{ 100, 100, 100 };
std::vector<size_t> steps{100*100, 100};
const char* data = new char[100*100*100];

// This constructor will call setSize() that accesses steps[2] which is out of bounds.
// But you may or may not see a crash from out of bounds access, so a reproducer
// may not be too helpful?
cv::Mat::Mat(sizes, CV_8UC1, data, steps.data());

@emmenlau
Copy link
Copy Markdown
Contributor Author

emmenlau commented Oct 1, 2020

Ok, maybe I know a better reproducer. We should be able to force the out-of-bounds to fail if we set the element to something that the element size is not a multiple of.

I have not tested this on my side but the following should fail, claiming that steps is not a multiple of the element size. The problem comes from the number 3 in steps, but this element should not be considered.

std::vector<int> sizes{ 100, 100, 100 };
std::vector<size_t> steps{ (2*100) * (2*100), (2*100), 3};
const char* data = new char[100*100*100];

// This constructor will call setSize() that accesses steps[2] which is out of bounds.
// But you may or may not see a crash from out of bounds access, so a reproducer
// may not be too helpful?
cv::Mat::Mat(sizes, CV_16UC1, data, steps.data());

@alalek
Copy link
Copy Markdown
Member

alalek commented Oct 1, 2020

Thank you!

OK, documentation says:

@param steps Array of ndims-1 steps ...

So your point is valid about the number of accessed elements in steps array.


Reproducer above is not fully correct due to:

-(2*100) * (2*100)
+100 * 100 * 2

Please put this test code into test_mat.cpp file:

TEST(Mat, regression_18473)
{
    std::vector<int> sizes(3);
    sizes[0] = 20;
    sizes[1] = 50;
    sizes[2] = 100;
#if 1  // with the fix
    std::vector<size_t> steps(2);
    steps[0] = 50*100*2;
    steps[1] = 100*2;
#else  // without the fix
    std::vector<size_t> steps(3);
    steps[0] = 50*100*2;
    steps[1] = 100*2;
    steps[2] = 2;
#endif
    std::vector<short> data(20*50*100, 0);  // 1Mb
    data[data.size() - 1] = 5;

    // param steps Array of ndims-1 steps
    Mat m(sizes, CV_16SC1, (void*)data.data(), (const size_t*)steps.data());

    ASSERT_FALSE(m.empty());
    EXPECT_EQ((int)5, (int)m.at<short>(99, 99, 99));
}

(adopted to C++98, see below)

Problem can be captured by valgrind on this test:

==372239== Invalid read of size 8
==372239==    at 0x6719DA0: cv::setSize(cv::Mat&, int, int const*, unsigned long const*, bool) (matrix.cpp:240)
==372239==    by 0x671B4A7: cv::Mat::Mat(std::vector<int, std::allocator<int> > const&, int, void*, unsigned long const*) (matrix.cpp:500)

or sporadic failures:

unknown file: Failure
C++ exception with description "OpenCV(3.4.12-pre) modules/core/src/matrix.cpp:242: error: (-13:Image step is wrong) Step must be a multiple of esz1 in function 'setSize'
" thrown in the test body.

As a bugfix this patch should go into 3.4 branch first.
We will merge changes from 3.4 into master regularly (weekly/bi-weekly).

So, please:

  • change "base" branch of this PR: master => 3.4 (use "Edit" button near PR title)
  • rebase your commits from master onto 3.4 branch. For example:
    git rebase -i --onto upstream/3.4 upstream/master
    (check list of your commits, save and quit (Esc + "wq" + Enter)
    where upstream is configured by following this GitHub guide and fetched (git fetch upstream).
  • push rebased commits into source branch of your fork (with --force option)

Note: no needs to re-open PR, apply changes "inplace".

@alalek
Copy link
Copy Markdown
Member

alalek commented Oct 4, 2020

@emmenlau Please allow to edit of this PR from maintainers or grab commits from here.

@emmenlau emmenlau force-pushed the bda_fix_cv_mat_steps branch from eeae9b7 to 102d8f6 Compare October 5, 2020 08:20
@emmenlau
Copy link
Copy Markdown
Contributor Author

emmenlau commented Oct 5, 2020

@emmenlau Please allow to edit of this PR from maintainers or grab commits from here.

Hi @alalek ! I can not find the option to allow edits from maintainers, is it possible that this has been set already? At least the checkbox as discussed in github docs is not available for me.

I also did not feel safe to rebase on https://github.com/alalek/opencv/commits/pr18473_r because rebase showed a conflict in modules/core/include/opencv2/core/version.hpp (a simple difference in the version number from 4.0.0 to 3.4.12).

Instead I cherry-picked my single change on top of origin/3.4. I hope that is good too?! Let me know if you need something else.

If, one the other hand, you are faster to take my changes yourself onto some branch/PR/... then please accept my permission to take the changes in any way you like for integration into opencv. Thanks!

Copy link
Copy Markdown
Member

@alalek alalek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done 👍

Will add test in a separate PR after merge.

@opencv-pushbot opencv-pushbot merged commit ebe9327 into opencv:3.4 Oct 5, 2020
This was referenced Oct 5, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants