Repository navigation
Fix #11765 FN: minsize not checked for string literal, buffer access out of bounds not found - #8944
chrchr-github wants to merge 9 commits into
Conversation
|
Test results for commit cd753bf (tools/test-my-pr.py, main compared to this PR): Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8944/ Posted automatically by the cppcheck PR test runner. +N: warnings only with this PR, -N: warnings only with main. The AI review is written by Claude and can be wrong. |
dmcppcheck
left a comment
There was a problem hiding this comment.
AI review
The PR adds new false positives. String literals now get a BUFFER_SIZE. As a result, every library minsize type="argvalue" on an input argument is checked against the literal's size. That is wrong for bounded string functions, which stop reading at the terminating NUL. Valid calls now get bufferAccessOutOfBounds warnings:
strncasecmp(s, "abc", 10)strnlen("abc", 100)_strnicmp(s, "abc", 10)strxfrm_l(d, "abc", 10, l)
The PR removes minsize from strxfrm, strncpy_s and strncat_s in std.cfg for exactly this reason. It leaves the same pattern in other places:
strxfrm_lin posix.cfg, which is the direct counterpart ofstrxfrmstrncasecmp,wcsncasecmp,strnlen/wcsnlenandwcswidthin posix.cfg_strnicmp*and_strncpy_s_l/_mbsncpy_sin windows.cfgsqlite3_stricmpin sqlite3.cfg
These configs need the same cleanup. The other option is to apply the literal size only for functions that are really memory-based (memcpy, memcmp, write, …). The new warnings on memory functions (memcmp(s, "abc", 10), write(fd, "abc", 10)) are correct. Wide, u8/U and concatenated literals give the expected sizes.
Testing
Built: make -j8 cppcheck testrunner in pr/ and make -j8 cppcheck in base/. Both built fine.
Unit tests: ./testrunner TestBufferOverrun passed: 198 tests, 0 failures.
Library test files: I ran test/cfg/{std.c,std.cpp,posix.c,gnu.c,bsd.c,windows.cpp} through the cppcheck of both base and the PR with the runtests.sh options. The output is the same on both, so no existing cfg test covers these cases.
False positives found: scratch/a.c:
#include <string.h>
#include <strings.h>
#include <locale.h>
int f1(const char *s) { return strncasecmp(s, "abc", 10); }
size_t f3(void) { return strnlen("abc", 100); }
int f4(const char *s) { return _strnicmp(s, "abc", 10); }
size_t f5(char *d, locale_t l) { return strxfrm_l(d, "abc", 10, l); }Command: cppcheck -q --library=posix --library=windows a.c
PR output:
a.c:5:47: warning: Buffer is accessed out of bounds: "abc" [bufferAccessOutOfBounds] (strncasecmp)
a.c:7:34: warning: Buffer is accessed out of bounds: "abc" [bufferAccessOutOfBounds] (strnlen)
a.c:8:45: warning: Buffer is accessed out of bounds: "abc" [bufferAccessOutOfBounds] (_strnicmp)
a.c:9:54: warning: Buffer is accessed out of bounds: "abc" [bufferAccessOutOfBounds] (strxfrm_l)
Base: no warnings. All four calls are valid, because these functions stop at the NUL.
Checked and correct:
memcmp(s,"abc",10)andwrite(fd,"abc",10)are now flagged. Both are real overreads.memcpy(d, "ab" "cd", 6)is OK and5is OK; I did not test a size large enough to overread this literal.memcpy(d, U"ab", 12)is OK and13is flagged.wcsncasecmp(s, L"abc", 10)andwcswidth(L"ab", 10)are not flagged here only because 10 is within the literal's size in bytes. They would be flagged with a larger bound.
Automated review of cd753bf by Claude. The pull request was built and tested locally.
| return bufSizeVal; | ||
| } | ||
| } | ||
| } else if (bufTok->tokType() == Token::Type::eString && bufTok->valueType()) { |
There was a problem hiding this comment.
AI review
With this branch, every input-argument minsize type="argvalue" in the library configs is now checked against string literals. That is wrong for bounded string functions, which stop reading at the NUL. Valid code now gets bufferAccessOutOfBounds, and base did not warn on any of these:
strncasecmp(s, "abc", 10); // posix.cfg
strnlen("abc", 100); // posix.cfg
_strnicmp(s, "abc", 10); // windows.cfg
strxfrm_l(d, "abc", 10, loc); // posix.cfg, same issue as the strxfrm entry removed from std.cfgThe same pattern is also in wcsncasecmp, wcsnlen and wcswidth (posix.cfg), _strncpy_s_l/_mbsncpy_s (windows.cfg) and sqlite3_stricmp (sqlite3.cfg). These configs need the same cleanup as std.cfg. The other option is to not use the literal size for strz arguments.
|
Test results for commit 9527653 (tools/test-my-pr.py, main compared to this PR): Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8944/ Posted automatically by the cppcheck PR test runner. +N: warnings only with this PR, -N: warnings only with main. The AI review is written by Claude and can be wrong. |
dmcppcheck
left a comment
There was a problem hiding this comment.
AI review
New false positives remain in library configs that the cleanup missed. The literal size is now checked against every input minsize type="argvalue". The latest commits removed it from the entries the earlier review listed, but three more bounded string functions still produce bufferAccessOutOfBounds on valid calls. Base does not warn on any of them:
_strnicmp_lin windows.cfg, args 1 and 2g_strncasecmpin gtk.cfg, args 1 and 2wcpncpyin posix.cfg, arg 2
The CI package run did not show these, but I reproduced them directly. Deleting the minsize entries one function at a time also has a cost. strnlen(buf, 3) on a char buf[2] that is not NUL-terminated was detected in base, and the PR turns that test in posix.c into a TODO; strncasecmp, strxfrm, strncpy_s and the others lose the same detection for real arrays. Skipping the literal size for <strz/> arguments in bufferOverflow() would fix the false positives without these false negatives. That is the alternative the earlier review proposed. Apart from this I found no new problems in the C++ change.
Testing
Builds: make -j8 cppcheck testrunner in pr/ and make -j8 cppcheck in base/. Both built fine.
Unit tests: ./testrunner TestBufferOverrun passed with 0 failures.
False positives that are still there
scratch/a.c:
#include <string.h>
#include <wchar.h>
#include <glib.h>
int f1(const char *s, _locale_t l) { return _strnicmp_l(s, "abc", 10, l); }
gint f2(const gchar *s) { return g_strncasecmp(s, "abc", 10); }
size_t f6(void) { const char buf[2]={'4','2'}; return strnlen(buf,3); }Command: cppcheck -q --library=posix --library=windows --library=gtk a.c
Base output:
a.c:9:63: error: Buffer is accessed out of bounds: buf [bufferAccessOutOfBounds] (strnlen on the non-terminated array)
PR output:
a.c:4:60: warning: Buffer is accessed out of bounds: "abc" [bufferAccessOutOfBounds] (_strnicmp_l)
a.c:5:51: warning: Buffer is accessed out of bounds: "abc" [bufferAccessOutOfBounds] (g_strncasecmp)
- The two calls the PR flags are valid, because these functions stop at the NUL.
- The PR no longer flags the real
strnlenoverread that base found. That is the TODO now in test/cfg/posix.c.
scratch/b.c:
#include <wchar.h>
wchar_t* f3(wchar_t *d) { return wcpncpy(d, L"ab", 20); }cppcheck -q --library=posix b.c reports b.c:2:45: warning: Buffer is accessed out of bounds: L"ab" with the PR and nothing with base. wcpncpy stops reading the source at the NUL, so this call is valid.
Also checked: memcpy(d, &"abc"[2], 4) and memcpy(d, "abc" + 2, 4) are not flagged. These are false negatives only, not false positives.
Script for the remaining entries: a short Python script lists every remaining minsize type="argvalue" on input or strz arguments in cfg/*.cfg; I used it to find the entries above.
Automated review of 9527653 by Claude. The pull request was built and tested locally.
| return bufSizeVal; | ||
| } | ||
| } | ||
| } else if (bufTok->tokType() == Token::Type::eString && bufTok->valueType()) { |
There was a problem hiding this comment.
AI review
This still causes false positives for bounded string functions whose library entries keep minsize type="argvalue" on input arguments:
_strnicmp_l(s, "abc", 10, l)(windows.cfg)g_strncasecmp(s, "abc", 10)(gtk.cfg)wcpncpy(d, L"ab", 20)(posix.cfg, arg 2)
All three now give bufferAccessOutOfBounds, and base gives nothing. Removing the minsize entries one function at a time also loses real detections on arrays that are not NUL-terminated (see the strnlen(buf,3) TODO in posix.c). Ignoring the literal size when the argument has <strz/>, or limiting it to non-strz arguments, would avoid both problems.
| const char buf[2]={'4','2'}; | ||
| size_t len = strnlen(buf,2); | ||
| // cppcheck-suppress bufferAccessOutOfBounds | ||
| // TODO cppcheck-suppress bufferAccessOutOfBounds |
There was a problem hiding this comment.
AI review
This is a regression. buf is {'4','2'}, which is not NUL-terminated, so strnlen(buf,3) really reads past the end, and base detected it. The minsize entry was removed from strnlen only so that the literal case stays quiet. The same detection is lost for strncasecmp, strxfrm, strncpy_s, strncat_s, sqlite3_stricmp and the others. If the literal size were not applied to strz arguments, the cfg entries and this check could stay.
No description provided.