Skip to content

Fix #11765 FN: minsize not checked for string literal, buffer access out of bounds not found - #8944

Open
chrchr-github wants to merge 9 commits into
cppcheck-opensource:mainfrom
chrchr-github:chr_11765
Open

chrchr-github wants to merge 9 commits into
cppcheck-opensource:mainfrom
chrchr-github:chr_11765

Conversation

@chrchr-github

Copy link
Copy Markdown
Collaborator

No description provided.

@chrchr-github
chrchr-github marked this pull request as ready for review October 15, 2026 19:17
@dmcppcheck

Copy link
Copy Markdown

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/
packages: 100
Differing warnings: 2 (+2 -0)
AI review: 2 improvement (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8944/2026-10-15_18-02-29-cd753bfd6b6e-review.txt)
Timing: -0.7% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8944/2026-10-15_18-02-29-cd753bfd6b6e_timing.html)

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 dmcppcheck 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.

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_l in posix.cfg, which is the direct counterpart of strxfrm
  • strncasecmp, wcsncasecmp, strnlen/wcsnlen and wcswidth in posix.cfg
  • _strnicmp* and _strncpy_s_l/_mbsncpy_s in windows.cfg
  • sqlite3_stricmp in 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) and write(fd,"abc",10) are now flagged. Both are real overreads.
  • memcpy(d, "ab" "cd", 6) is OK and 5 is OK; I did not test a size large enough to overread this literal.
  • memcpy(d, U"ab", 12) is OK and 13 is flagged.
  • wcsncasecmp(s, L"abc", 10) and wcswidth(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.

Comment thread lib/checkbufferoverrun.cpp Outdated
@dmcppcheck

Copy link
Copy Markdown

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/
packages: 100
Differing warnings: 0 (+0 -0)
Timing: -0.3% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8944/2026-10-16_06-51-06-95276538498c_timing.html)

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 dmcppcheck 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.

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_l in windows.cfg, args 1 and 2
  • g_strncasecmp in gtk.cfg, args 1 and 2
  • wcpncpy in 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 strnlen overread 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.

Comment thread lib/checkbufferoverrun.cpp Outdated
Comment thread test/cfg/posix.c Outdated

@dmcppcheck dmcppcheck 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.

AI review

The <strz/> check does not stop the false positives on bounded string functions. Those library entries don't mark their string arguments <strz/>. The current version puts back the cfg entries and skips the literal size only when isargstrz() is true. But strnlen/wcsnlen, strncasecmp/wcsncasecmp, wcpncpy (arg 2), _strnicmp*, _strnicmp_l*, g_strncasecmp, _strncpy_s_l/_mbsncpy_s and others have minsize type="argvalue" on an in argument that has no <strz/>. So valid calls such as strnlen("abc", 100) and strncasecmp(s, "abc", 10) are again reported as bufferAccessOutOfBounds, and base reports nothing on them.

There are two ways to fix this:

  • Add <strz/> to those arguments in the cfg files. Then the real overread on non-terminated arrays (strnlen(buf,3)) is still found.
  • Limit the literal size to functions that work on raw memory.

The new line strnlen("ab",3) in test/cfg/posix.c doesn't test this: the bound 3 equals the literal size, so it passes, while strnlen("ab",4) is flagged. 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: 198 tests, 0 failures.

cfg test: test/cfg/posix.c gives no warning on the new line 722, strnlen("ab",3), because 3 is not larger than the literal's size.

False positives: scratch/a.c:

#include <string.h>
#include <strings.h>
#include <wchar.h>
int f1(const char *s) { return strncasecmp(s, "abc", 10); }
size_t f2(void) { return strnlen("abc", 100); }
int f3(const char *s) { return _strnicmp(s, "abc", 10); }
wchar_t* f4(wchar_t *d) { return wcpncpy(d, L"ab", 20); }
int f5(const char *s) { return g_strncasecmp(s, "abc", 10); }
void f6(lua_State *L) { lua_pushlstring(L, "abc", 10); }
int f7(const char *s) { return memcmp(s, "abc", 10); }
char *f8(char *d) { return strncpy(d, "ab", 20); }
char *f9(char *d) { return strncat(d, "ab", 20); }
int f10(const char *s) { return strncmp(s, "abc", 10); }

Command: cppcheck -q --library=posix --library=windows --library=gtk --library=lua a.c

  • Base: no output.
  • PR: bufferAccessOutOfBounds on lines 4, 5, 6, 7, 8, 9 and 10.
    • Lines 4–8 are false positives, because these functions stop at the NUL.
    • Lines 9 (lua_pushlstring) and 10 (memcmp) are correct new detections.
    • strncpy, strncat and strncmp stay quiet because their arguments have <strz/>.

Finding the affected entries: a small Python script lists the minsize type="argvalue" entries on non-output arguments without <strz/> in cfg/*.cfg. Besides the functions above it found wcsncasecmp, _wcsnicmp*, _mbsnicmp*, _strncpy_s_l, _wcsncpy_s_l, _mbsncpy_s and _mbsncpy_s_l.


Automated review of 5ec129e by Claude. The pull request was built and tested locally.

Comment thread lib/checkbufferoverrun.cpp
Comment thread test/cfg/posix.c Outdated
@dmcppcheck

Copy link
Copy Markdown

Test results for commit 5ec129e (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/
packages: 100
Differing warnings: 2 (+2 -0)
AI review: 2 regression (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8944/2026-10-17_21-12-29-5ec129e2a362-review.txt)
Timing: -0.0% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8944/2026-10-17_21-12-29-5ec129e2a362_timing.html)

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 dmcppcheck 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.

AI review

The new <strz/> on strncasecmp in posix.cfg causes a new false positive. <strz/> makes CheckFunctions require a NUL-terminated argument. strncasecmp reads at most n bytes, so a char array without a terminator, or &c, is valid when n is no larger than the buffer. The PR now reports both cases as invalidFunctionArgStr errors, and base reports nothing. So adding <strz/> to the bounded string functions that the earlier review listed is not a free fix either: it trades the literal false positive for this one. A narrower option is to skip the literal size for these functions in bufferOverflow(), for example when the argument is marked <strz/> or has a minsize tied to a length argument, without changing the cfg.

The other remaining gaps were already raised in comments.md: strnlen, _strnicmp, wcpncpy and g_strncasecmp still have no <strz/>, and strnlen("ab",3) does not test the change. I found no other 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 TestFunctions reported 0 failures.
  • cfg test: test/cfg/posix.c with --library=posix --check-library --enable=style,information --inline-suppr --error-exitcode=1 exits with 0.

New false positive from adding <strz/> to strncasecmp. scratch/s.c:

#include <strings.h>
int f1(const char *s) {
    char buf[3] = {'a','b','c'};
    return strncasecmp(buf, s, 3);
}
int f2(const char *s) {
    char c = 'a';
    return strncasecmp(&c, s, 1);
}

Command: cppcheck -q --library=posix s.c

  • Base: no output.
  • PR:
s.c:4:24: error: Invalid strncasecmp() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]
s.c:8:24: error: Invalid strncasecmp() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]

Both calls are valid because strncasecmp reads at most n bytes.


Automated review of 1fa524a by Claude. The pull request was built and tested locally.

Comment thread cfg/posix.cfg
@dmcppcheck

Copy link
Copy Markdown

Test results for commit 1fa524a (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/
packages: 100
Differing warnings: 0 (+0 -0)
Timing: -0.0% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8944/2026-10-18_08-00-11-1fa524a1bbaf_timing.html)

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 dmcppcheck 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.

AI review

The new hasMinSize() exception in CheckFunctions::invalidFunctionUsage() turns off invalidFunctionArgStr for every <strz/> argument that also has a <minsize>. It doesn't only cover the bounded comparison functions it was added for. That includes the destination of strcat, strncat, wcsncat, strcat_s, strlcat, _mbsncat and others, where a NUL terminator really is needed. So the PR loses real detections: char buf[3] = {'a','b','c'}; strcat(buf, "x"); and strncat(d, "x", 1) on a non-terminated d were reported by base and are silent with the PR. No test in test/cfg covers these cases, so CI does not catch it. Limiting the exception to direction="in" arguments with minsize type="argvalue" (or dropping the global exception and handling bounded functions differently) would avoid this.

I found no new problems in the getBufferSize() change beyond what comments.md already has.

Testing

Builds: make -j8 cppcheck testrunner in pr/ and make -j8 cppcheck in base/. Both built fine.

Unit tests: ./testrunner TestBufferOverrun TestFunctions gave Tests failed: 0.

cfg tests: I ran test/cfg/{std.c,std.cpp,posix.c,windows.cpp,bsd.c} through both binaries with --inline-suppr. In the PR there is no remaining invalidFunctionArgStr output. In base, the only output is the 3 strxfrm lines whose suppressions the PR removes. No cfg test covers strz on a strcat/strncat destination.

Affected entries: a small script lists every arg in cfg/*.cfg that has both <strz/> and <minsize>. The PR now skips the strz check for all of them. Among them are strcat arg1 (minsize strlen), strncat/wcsncat arg1, strcat_s/wcscat_s arg1, strlcat arg1, _mbscat_s, _mbsncat, _mbsncat_l, _wcsncat_l arg1, strncat_s/strncpy_s arg3, sethostname and mq_timedsend.

Lost detections (false negatives), scratch/a.c:

#include <string.h>
void f1(void) { char buf[3] = {'a','b','c'}; strcat(buf, "x"); }
void f2(void) { char c = 'a'; strcat(&c, "x"); }
void f3(void) { char buf[10] = {'a','b','c'}; char d[3]={'a','b','c'}; strncat(d, "x", 1); }
void f4(void) { char c = 'a'; strncat(&c, "", 1); }

Command: cppcheck -q --library=posix a.c

Base:

a.c:3:38: error: Buffer is accessed out of bounds: &c [bufferAccessOutOfBounds]
a.c:2:53: error: Invalid strcat() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]
a.c:3:38: error: Invalid strcat() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]
a.c:4:80: error: Invalid strncat() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]
a.c:5:39: error: Invalid strncat() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]

PR:

a.c:3:38: error: Buffer is accessed out of bounds: &c [bufferAccessOutOfBounds]

All four invalidFunctionArgStr errors are lost. They are real bugs, because strcat/strncat look for the NUL terminator in the destination.


Automated review of 558148b by Claude. The pull request was built and tested locally.

Comment thread lib/checkfunctions.cpp Outdated
@dmcppcheck

Copy link
Copy Markdown

Test results for commit 558148b (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/
packages: 100
Differing warnings: 0 (+0 -0)
Timing: +0.0% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8944/2026-10-18_20-02-44-558148b67ecc_timing.html)

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 dmcppcheck 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.

AI review

I found two new problems in the string-literal branch of getBufferSize(). Neither causes false positives, so neither is a blocker.

  1. A definite overread of a literal is reported at the wrong severity. The new BUFFER_SIZE value is never set to known, and ValueFlow::Value starts out as Possible. So memcpy(d, "abc", 5) is reported as a warning, while the same overread on char a[4] is reported as an error. The mymemset("abc", 0, 20) TODO in testbufferoverrun.cpp originally expected (error). The PR changed that expectation to (warning) without saying why.
  2. The index in &"abc"[i] is ignored. getBufferSize() reads index and then returns the whole literal size, so memcpy(d, &"abc"[2], 3) is not reported. The array branch subtracts the index (dim - index).

Everything else I checked works. The cfg <strz/> additions and the ARGVALUE-only hasArgMinSize() now keep strnlen("ab",5) and strncasecmp(s,"abc",10) quiet. They also keep the strcat destination check. The issues already raised in comments.md are not repeated here: the lost strncat destination check, and wcsncasecmp, _strnicmp_l and _strncpy_s_l without <strz/>.

Testing

Builds: make -j8 cppcheck testrunner in pr/ and make -j8 cppcheck in base/ both succeeded.

Unit tests: ./testrunner TestBufferOverrun TestFunctions reported Tests failed: 0.

cfg entries: a script listed every arg in cfg/*.cfg that has <strz/> together with an argvalue minsize, for base and for the PR. The only difference is the new entries: strncasecmp, strnlen/wcsnlen, wcpncpy arg 2, _strnicmp* and g_strncasecmp.

Mixed inputs (scratch/a.c, --library=posix,gtk,gnu):

  • Now reported, all real overreads: memcmp(s,"abc",10), fwrite("abc",1,10,stdout), fwrite("abc",10,1,stdout), memcpy(buf,"abc",sizeof(buf)), memcpy(d,"a\0b",5) and g_string_new_len("abc",10).
  • Not reported, as expected: strnlen("ab",5), strncasecmp(s,"abc",10), strncmp(s,"abc",10) and g_string_new_len("abc",-1).

Severity (scratch/c.cpp):

#include <cstring>
void f3(char *d) { char a[4] = "abc"; memcpy(d, a, 5); }
void f4(char *d) { memcpy(d, "abc", 5); }

Command: pr/cppcheck -q --enable=warning c.cpp

c.cpp:2:49: error: Buffer is accessed out of bounds: a [bufferAccessOutOfBounds]
c.cpp:3:30: warning: Buffer is accessed out of bounds: "abc" [bufferAccessOutOfBounds]

Index ignored (scratch/b.c):

#include <string.h>
void s2(char *d) { memcpy(d, &"abc"[2], 3); }
void s3(char *d) { memcpy(d, &"abc"[3], 2); }
void a1(char *d) { char a[4]="abc"; memcpy(d, &a[2], 3); }

Command: pr/cppcheck -q --enable=warning b.c

Only a1 is reported (error: Buffer is accessed out of bounds: &a[2]). s2 and s3 are real overreads and get no warning.


Automated review of 062dd28 by Claude. The pull request was built and tested locally.

Comment thread lib/checkbufferoverrun.cpp
Comment thread lib/checkbufferoverrun.cpp Outdated
@dmcppcheck

Copy link
Copy Markdown

Test results for commit 062dd28 (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/
packages: 100
Differing warnings: 0 (+0 -0)
Timing: +0.2% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8944/2026-10-19_07-45-15-062dd28010e7_timing.html)

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants