Skip to content

fix(wechat_qrcode): Init nBytes after the count value is determined#3480

Merged
vpisarev merged 6 commits intoopencv:4.xfrom
Konano:patch-1
Apr 26, 2023
Merged

fix(wechat_qrcode): Init nBytes after the count value is determined#3480
vpisarev merged 6 commits intoopencv:4.xfrom
Konano:patch-1

Conversation

@Konano
Copy link
Copy Markdown
Contributor

@Konano Konano commented Apr 24, 2023

Merge with test case: opencv/opencv_extra#1061

A malicious crafted image will crash the wechat_qrcode module by invalid memory access.

The earliest PoC by @GZTimeWalker: https://gist.github.com/GZTimeWalker/3ca70a8af2f5830711e9cccc73fb5270

Sample image:

image

In DecodedBitStreamParser::decodeByteSegment, an attacker can add a byte segment with the Character Count Indicator of non-zero but empty content at the end of the data segment, where available is 0 and count is updated to 0, but nBytes remains non-zero.

int nBytes = count;
BitSource& bits(*bits_);
// Don't crash trying to read more bits than we have available.
int available = bits.available();
// try to repair count data if count data is invalid
if (count * 8 > available) {
count = (available + 7 / 8);
}

Since count is 0, the readBytes in L203 is a null pointer.

ArrayRef<char> bytes_(count);
char* readBytes = &(*bytes_)[0];

Then DecodedBitStreamParser::decodeByteSegment will call DecodedBitStreamParser::append, where the nBytes parameter is not 0.

Eventually the program will crash at line 108 because it accesses a null pointer.

We think the essence of the issue is that count was modified without updating nBytes, resulting in inconsistent values for the two variables. Unlike #3479, we think that the initialization of nBytes should be done after the value of count has been determined, which helps avoid other potential errors.

Moreover, we also found some wrong calculations in line 199, which are corrected together in this PR.

Also fixed #3478.

Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

  • 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 another license that is incompatible with OpenCV
  • The PR is proposed to the proper branch
  • There is a reference to the 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

@ssst0n3
Copy link
Copy Markdown

ssst0n3 commented Apr 24, 2023

Amazing, too fast

@NanoWraith
Copy link
Copy Markdown

Orz!!

@TUGOhost
Copy link
Copy Markdown

amazing

@syuy
Copy link
Copy Markdown

syuy commented Apr 24, 2023

Cool!

@Lakr233
Copy link
Copy Markdown

Lakr233 commented Apr 24, 2023

LGTM

@helin0815
Copy link
Copy Markdown

太猛了哥

@KarKLi
Copy link
Copy Markdown

KarKLi commented Apr 24, 2023

amazing!

@KongWeibin
Copy link
Copy Markdown

interesting

@Timon-2022
Copy link
Copy Markdown

niuwa!!!!mobai dalao!

@Naville
Copy link
Copy Markdown

Naville commented Apr 24, 2023

ChatGPT 3.5:

What's the bug in this code?

int available = bits.available();
     // try to repair count data if count data is invalid
     if (count * 8 > available) {
         count = (available + 7 / 8);
     }

     ArrayRef<char> bytes_(count);

There is a bug in the line count = (available + 7 / 8);.
The expression 7/8 will be evaluated as integer division, resulting in a value of 0. Therefore, count will always be set to > available rather than being adjusted to the correct number of bytes needed to store the count data.
To fix the bug, the expression should be count = (available + 7) / 8;, which will correctly round up to the nearest integer number of bytes needed to store the count data.

@yilongli-tubi
Copy link
Copy Markdown

Amazing! This bug seems to just crash my WeChat client program...

@yoghurt-lee
Copy link
Copy Markdown

amazing!

@Nagico
Copy link
Copy Markdown

Nagico commented Apr 24, 2023

But today is not Thursday😂

@DapengFeng
Copy link
Copy Markdown

LGTM

@sfc9982
Copy link
Copy Markdown

sfc9982 commented Apr 24, 2023

Long live these volunteers! ❤️

@UPON-2021
Copy link
Copy Markdown

amazing!

@Apple-QAQ
Copy link
Copy Markdown

Too Fast!

@xieyumc
Copy link
Copy Markdown

xieyumc commented Apr 24, 2023

QQ和微信都会crash

@NoahZhang1
Copy link
Copy Markdown

Cool!! Really fast

Copy link
Copy Markdown

@mochaaP mochaaP left a comment

Choose a reason for hiding this comment

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

lgtm

@Konano
Copy link
Copy Markdown
Contributor Author

Konano commented Apr 24, 2023

@opencv-alalek @asmorkalov Would it be possible to review and merge of my pull request? Thank you very much!

@HRan2004
Copy link
Copy Markdown

666

@RJHD-GIT
Copy link
Copy Markdown

有品

@WanliZhong WanliZhong mentioned this pull request Apr 25, 2023
6 tasks
@JulianSong
Copy link
Copy Markdown

LGTM

Copy link
Copy Markdown
Member

@WanliZhong WanliZhong left a comment

Choose a reason for hiding this comment

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

@Konano @GZTimeWalker Hi, thanks for your contribution! Your patch will be the main branch to fix this bug. There are some comments:

  1. put a test image to opencv_extra/testdata/cv/qrcode/
  2. create a new pr to opencv_extra which branch name should be patch-1 in Konano’s repository.
  3. then update your code in opencv_contrib, the CI test will automatically pull the new test data to test your pr
  4. add test code in modules/wechat_qrcode/test/test_qrcode.cpp
    TEST(Objdetect_QRCode_bug, issue_3478) {
        auto detector = wechat_qrcode::WeChatQRCode();
        std::string image_path = findDataFile("qrcode/issue_3478.png");
        Mat src = imread(image_path, IMREAD_GRAYSCALE);
        ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
        std::vector<std::string> outs = detector.detectAndDecode(src);
        ASSERT_EQ(1, (int) outs.size());
        ASSERT_EQ(16, (int) outs[0].size());
        ASSERT_EQ("KFCVW50         ", outs[0]);
    }
  5. after testing, your method does not pass the test sample on windows platform
  6. I suggest you add code at Line 202
    // issue https://github.com/opencv/opencv_contrib/issues/3478
    if (bytes_->empty())
        return;

@Konano
Copy link
Copy Markdown
Contributor Author

Konano commented Apr 25, 2023

@WanliZhong Thank you for your help!

You mentioned that our method did not pass the test sample on windows platform, so I checked the test logs and the error message for test failure is as follows:

unknown file: error: C++ exception with description "OpenCV(4.7.0-dev) C:\build\precommit-contrib_windows64\4.x\opencv\modules\ts\src\ts.cpp:1071: error: (-2:Unspecified error) OpenCV tests: Can't find required data file: qrcode/issue_3478.png in function 'cvtest::findData'

" thrown in the test body.

It looks like the new test case is not loaded?

@WanliZhong
Copy link
Copy Markdown
Member

@WanliZhong Thank you for your help!

You mentioned that our method did not pass the test sample on windows platform, so I checked the test logs and the error message for test failure is as follows:

unknown file: error: C++ exception with description "OpenCV(4.7.0-dev) C:\build\precommit-contrib_windows64\4.x\opencv\modules\ts\src\ts.cpp:1071: error: (-2:Unspecified error) OpenCV tests: Can't find required data file: qrcode/issue_3478.png in function 'cvtest::findData'

" thrown in the test body.

It looks like the new test case is not loaded?

Before runing the test, you should add an env variable to refer the test data OPENCV_TEST_DATA_PATH=path/to/opencv_extra/testdata

@GZTimeWalker
Copy link
Copy Markdown
Contributor

GZTimeWalker commented Apr 25, 2023

@WanliZhong All changes are applied, and we updated the branch https://github.com/Konano/opencv_extra/tree/patch-1

The error on windows x64 are caused by the wrong environment variables, we tested the code localy, it works fine.

image

@WanliZhong
Copy link
Copy Markdown
Member

WanliZhong commented Apr 25, 2023

Good! Let's check if the test can be passed on all platform

@Henryk07
Copy link
Copy Markdown

so fast!! 厉害!

Copy link
Copy Markdown
Member

@zihaomu zihaomu left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution! 👍

@zihaomu
Copy link
Copy Markdown
Member

zihaomu commented Apr 25, 2023

Please take a look. @asmorkalov, @opencv-alalek, @vpisarev.
This error only occurs in wechat_qrcode, but not in the qrcode of opencv/opencv.

@WanliZhong
Copy link
Copy Markdown
Member

WanliZhong commented Apr 25, 2023

There is a slight difference between qrcode and wechat_qrcode. qrcode module will return an empty string, since the qrcode is considered to be wrong case. But wechat_qrcode will ignore the error and return "KFCVW50 "

@GZTimeWalker
Copy link
Copy Markdown
Contributor

There is a slight difference between qrcode and wechat_qrcode. qrcode module will return an empty string, since the qrcode is considered to be wrong case. But wechat_qrcode will ignore the error and return "KFCVW50 "

From my point of view, since the data in the mixed mode of the QRCode is parsed from many segments, a decoder should have to output as many partially correct results as possible.

Comment on lines +201 to +206
size_t nBytes = count;

ArrayRef<char> bytes_(nBytes);
// issue https://github.com/opencv/opencv_contrib/issues/3478
if (bytes_->empty())
return;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If I understand correctly, bytes_->empty() should be equivalent to nBytes == 0. So why not using nBytes == 0 for simplicity and efficiency?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think this change would be a more visual indication of the intention to avoid null pointers?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yes

@Konano
Copy link
Copy Markdown
Contributor Author

Konano commented Apr 25, 2023

🎉 All test cases on all platform have been passed!

@view6view
Copy link
Copy Markdown

tql,大佬

@hanxu317317
Copy link
Copy Markdown

昨天刚发现. 今天就修复~够速度 .

@7a3lv7
Copy link
Copy Markdown

7a3lv7 commented Apr 26, 2023

Amazing

@Konano
Copy link
Copy Markdown
Contributor Author

Konano commented Apr 26, 2023

It seems that everything is done. Can you please review and merge? @alalek.

@zihaomu zihaomu added the test label Apr 26, 2023
@vpisarev vpisarev self-requested a review April 26, 2023 07:08
@vpisarev vpisarev merged commit ccc2772 into opencv:4.x Apr 26, 2023
@hcqbuqingzhen
Copy link
Copy Markdown

NIU 大佬

@karzeoy
Copy link
Copy Markdown

karzeoy commented Apr 26, 2023

amazing!

@Takeoff0518
Copy link
Copy Markdown

OrzOrzOrzOrz

@Konano
Copy link
Copy Markdown
Contributor Author

Konano commented Apr 26, 2023

This pr has too much attention on the internet and has been merged, maybe we should lock it to avoid too many pointless comments. @WanliZhong @zihaomu

@opencv opencv locked as resolved and limited conversation to collaborators Apr 26, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

接入微信二维码识别引擎后 这个二维码会crash 微信线上包也一样