Skip to content

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

Merged
vpisarev merged 6 commits into
opencv: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 into
opencv:4.xfrom
Konano:patch-1

Conversation

@Konano

@Konano Konano commented Apr 24, 2023

Copy link
Copy Markdown
Contributor

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

ssst0n3 commented Apr 24, 2023

Copy link
Copy Markdown

Amazing, too fast

@NanoWraith

Copy link
Copy Markdown

Orz!!

@TUGOhost

Copy link
Copy Markdown

amazing

@syuy

syuy commented Apr 24, 2023

Copy link
Copy Markdown

Cool!

@Lakr233

Lakr233 commented Apr 24, 2023

Copy link
Copy Markdown

LGTM

@helin0815

Copy link
Copy Markdown

太猛了哥

@KarKLi

KarKLi commented Apr 24, 2023

Copy link
Copy Markdown

amazing!

@KongWeibin

Copy link
Copy Markdown

interesting

@Timon-2022

Copy link
Copy Markdown

niuwa!!!!mobai dalao!

@Naville

Naville commented Apr 24, 2023

Copy link
Copy Markdown

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

Nagico commented Apr 24, 2023

Copy link
Copy Markdown

But today is not Thursday😂

@DapengFeng

Copy link
Copy Markdown

LGTM

@sfc9982

sfc9982 commented Apr 24, 2023

Copy link
Copy Markdown

Long live these volunteers! ❤️

@UPON-2021

Copy link
Copy Markdown

amazing!

@Apple-QAQ

Copy link
Copy Markdown

Too Fast!

@xieyumc

xieyumc commented Apr 24, 2023

Copy link
Copy Markdown

QQ和微信都会crash

@NoahZhang1

Copy link
Copy Markdown

Cool!! Really fast

@mochaaP mochaaP left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

lgtm

@Konano

Konano commented Apr 24, 2023

Copy link
Copy Markdown
Contributor Author

@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

@WanliZhong WanliZhong left a comment

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.

@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

Konano commented Apr 25, 2023

Copy link
Copy Markdown
Contributor Author

@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

GZTimeWalker commented Apr 25, 2023

Copy link
Copy Markdown
Contributor

@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

WanliZhong commented Apr 25, 2023

Copy link
Copy Markdown
Member

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

@Henryk07

Copy link
Copy Markdown

so fast!! 厉害!

@zihaomu zihaomu left a comment

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.

Thanks for your contribution! 👍

@zihaomu

zihaomu commented Apr 25, 2023

Copy link
Copy Markdown
Member

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

@WanliZhong

WanliZhong commented Apr 25, 2023

Copy link
Copy Markdown
Member

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

Konano commented Apr 25, 2023

Copy link
Copy Markdown
Contributor Author

🎉 All test cases on all platform have been passed!

@view6view

Copy link
Copy Markdown

tql,大佬

@hanxu317317

Copy link
Copy Markdown

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

@7a3lv7

7a3lv7 commented Apr 26, 2023

Copy link
Copy Markdown

Amazing

@Konano

Konano commented Apr 26, 2023

Copy link
Copy Markdown
Contributor Author

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

karzeoy commented Apr 26, 2023

Copy link
Copy Markdown

amazing!

@Takeoff0518

Copy link
Copy Markdown

OrzOrzOrzOrz

@Konano

Konano commented Apr 26, 2023

Copy link
Copy Markdown
Contributor Author

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 微信线上包也一样