mirror of
https://github.com/opencv/opencv.git
synced 2026-07-21 19:33:03 +04:00
Merge pull request #29378 from uwezkhan:qr-alpha-map-bound
bound alphanumeric values in qr decodeAlpha before map lookup #29378 decodeAlpha in the no-quirc QR backend reads alphanumeric symbols from the post-ECC bitstream and indexes a fixed 45-entry map[] with the raw values. The 11-bit pair from next(11) goes up to 2047, so tuple/45 lands on 45 once the pair passes 2024, and the 6-bit trailing char from next(6) goes up to 63, so map[value] runs out to map[63]. A QR whose data codewords carry an alphanumeric segment with one of those out-of-range values reads past the static array, and the stray byte lands in the string returned by detectAndDecode. WITH_QUIRC defaults off, so this is the decoder a stock build runs. Before, the only guard was on the encode side, which never emits those values, so the decoder trusted the stream and indexed map[] directly. New version checks value range and return empty string for malformed qr codes. ### Pull Request Readiness Checklist - [x] I agree to contribute to the project under Apache 2 License. - [x] 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 - [x] 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 - [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
@@ -1359,11 +1359,11 @@ private:
|
||||
void extractCodewords(Mat& source, std::vector<uint8_t>& codewords);
|
||||
bool errorCorrection(std::vector<uint8_t>& codewords);
|
||||
bool errorCorrectionBlock(std::vector<uint8_t>& codewords);
|
||||
void decodeSymbols(String& result);
|
||||
bool decodeSymbols(String& result);
|
||||
void decodeNumeric(String& result);
|
||||
void decodeAlpha(String& result);
|
||||
bool decodeAlpha(String& result);
|
||||
void decodeByte(String& result);
|
||||
void decodeECI(String& result);
|
||||
bool decodeECI(String& result);
|
||||
void decodeKanji(String& result);
|
||||
void decodeStructuredAppend(String& result);
|
||||
};
|
||||
@@ -1472,7 +1472,10 @@ bool QRCodeDecoderImpl::run(const Mat& straight, String& decoded_info) {
|
||||
if (!errorCorrection(bitstream.data)) {
|
||||
return false;
|
||||
}
|
||||
decodeSymbols(decoded_info);
|
||||
if (!decodeSymbols(decoded_info)) {
|
||||
decoded_info = "";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1737,7 +1740,7 @@ void QRCodeDecoderImpl::extractCodewords(Mat& source, std::vector<uint8_t>& code
|
||||
}
|
||||
}
|
||||
|
||||
void QRCodeDecoderImpl::decodeSymbols(String& result) {
|
||||
bool QRCodeDecoderImpl::decodeSymbols(String& result) {
|
||||
CV_Assert(!bitstream.empty());
|
||||
|
||||
// Decode depends on the mode
|
||||
@@ -1750,15 +1753,19 @@ void QRCodeDecoderImpl::decodeSymbols(String& result) {
|
||||
}
|
||||
|
||||
if (currMode == 0 || bitstream.empty())
|
||||
return;
|
||||
return true;
|
||||
if (currMode == QRCodeEncoder::EncodeMode::MODE_NUMERIC)
|
||||
decodeNumeric(result);
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_ALPHANUMERIC)
|
||||
decodeAlpha(result);
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_ALPHANUMERIC) {
|
||||
if (!decodeAlpha(result))
|
||||
return false;
|
||||
}
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_BYTE)
|
||||
decodeByte(result);
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_ECI)
|
||||
decodeECI(result);
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_ECI) {
|
||||
if (!decodeECI(result))
|
||||
return false;
|
||||
}
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_KANJI)
|
||||
decodeKanji(result);
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_STRUCTURED_APPEND) {
|
||||
@@ -1769,6 +1776,7 @@ void QRCodeDecoderImpl::decodeSymbols(String& result) {
|
||||
else
|
||||
CV_Error(Error::StsNotImplemented, format("mode %d", currMode));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void QRCodeDecoderImpl::decodeNumeric(String& result) {
|
||||
@@ -1788,7 +1796,7 @@ void QRCodeDecoderImpl::decodeNumeric(String& result) {
|
||||
}
|
||||
}
|
||||
|
||||
void QRCodeDecoderImpl::decodeAlpha(String& result) {
|
||||
bool QRCodeDecoderImpl::decodeAlpha(String& result) {
|
||||
static const char map[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
||||
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
|
||||
@@ -1798,13 +1806,18 @@ void QRCodeDecoderImpl::decodeAlpha(String& result) {
|
||||
int num = bitstream.next(version <= 9 ? 9 : (version <= 26 ? 11 : 13));
|
||||
for (int i = 0; i < num / 2; ++i) {
|
||||
int tuple = bitstream.next(11);
|
||||
if (tuple >= 45 * 45)
|
||||
return false;
|
||||
result += map[tuple / 45];
|
||||
result += map[tuple % 45];
|
||||
}
|
||||
if (num % 2) {
|
||||
int value = bitstream.next(6);
|
||||
if (value >= 45)
|
||||
return false;
|
||||
result += map[value];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void QRCodeDecoderImpl::decodeByte(String& result) {
|
||||
@@ -1814,7 +1827,7 @@ void QRCodeDecoderImpl::decodeByte(String& result) {
|
||||
}
|
||||
}
|
||||
|
||||
void QRCodeDecoderImpl::decodeECI(String& result) {
|
||||
bool QRCodeDecoderImpl::decodeECI(String& result) {
|
||||
int eciAssignValue = bitstream.next(8);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (eciAssignValue & 1 << (7 - i))
|
||||
@@ -1825,8 +1838,7 @@ void QRCodeDecoderImpl::decodeECI(String& result) {
|
||||
if (this->eci == 0) {
|
||||
this->eci = static_cast<QRCodeEncoder::ECIEncodings>(eciAssignValue);
|
||||
}
|
||||
decodeSymbols(result);
|
||||
|
||||
return decodeSymbols(result);
|
||||
}
|
||||
|
||||
void QRCodeDecoderImpl::decodeKanji(String& result) {
|
||||
|
||||
@@ -571,6 +571,54 @@ TEST(Objdetect_QRCode_decode, decode_regression_version_25)
|
||||
EXPECT_EQ(expect_msg, decoded_msg);
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_decode, decode_alphanumeric_out_of_range)
|
||||
{
|
||||
// Version 1 QR with a valid Reed-Solomon block whose alphanumeric segment
|
||||
// carries an 11-bit pair value of 2047. decodeAlpha computes 2047 / 45 == 45
|
||||
// and used to read map[45], one past the 45-entry table, leaking an adjacent
|
||||
// byte into the decoded string. The decoder must reject it and return an
|
||||
// empty string instead.
|
||||
static const char* modules[21] = {
|
||||
"000000011011010000000",
|
||||
"011111010011010111110",
|
||||
"010001011011010100010",
|
||||
"010001010011010100010",
|
||||
"010001011011010100010",
|
||||
"011111010011010111110",
|
||||
"000000010101010000000",
|
||||
"111111111011011111111",
|
||||
"000001000011001010101",
|
||||
"111111101001011011000",
|
||||
"000111010001011011000",
|
||||
"001001100111011011000",
|
||||
"000001011011011011000",
|
||||
"111111110101011011000",
|
||||
"000000010111011011001",
|
||||
"011111011101011011001",
|
||||
"010001010001011011011",
|
||||
"010001010001011011011",
|
||||
"010001010001011011011",
|
||||
"011111010001011011010",
|
||||
"000000010001011011011"
|
||||
};
|
||||
Mat qr(21, 21, CV_8UC1);
|
||||
for (int i = 0; i < 21; i++)
|
||||
for (int j = 0; j < 21; j++)
|
||||
qr.at<uchar>(i, j) = modules[i][j] == '1' ? 255 : 0;
|
||||
|
||||
Mat src;
|
||||
cv::resize(qr, src, qr.size() * 10, 0, 0, INTER_NEAREST);
|
||||
cv::copyMakeBorder(src, src, 40, 40, 40, 40, BORDER_CONSTANT, Scalar(255));
|
||||
|
||||
QRCodeDetector qrcode;
|
||||
std::vector<Point> corners;
|
||||
ASSERT_TRUE(qrcode.detect(src, corners));
|
||||
Mat straight_barcode;
|
||||
std::string decoded_info;
|
||||
EXPECT_NO_THROW(decoded_info = qrcode.decode(src, corners, straight_barcode));
|
||||
EXPECT_TRUE(decoded_info.empty());
|
||||
}
|
||||
|
||||
TEST_P(Objdetect_QRCode_detectAndDecodeMulti, decode_9_qrcodes_version7)
|
||||
{
|
||||
const std::string name_current_image = "9_qrcodes_version7.jpg";
|
||||
|
||||
Reference in New Issue
Block a user