From b00858c113a54ef5899ca9e92a6ed301e58b16be Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Sun, 19 Apr 2026 18:25:06 +0300 Subject: [PATCH 1/7] safestringlib: fix wchar bound checking Wchar APIs do wchar bound checking by multiplying to sizeof(wchar_t). This may lead to improper management of edge conditions. Rewrite such checks to avoid multiplication. Signed-off-by: Alexander Usyskin --- safeclib/wcpcpy_s.c | 2 +- safeclib/wcscat_s.c | 2 +- safeclib/wcscpy_s.c | 2 +- safeclib/wcsncat_s.c | 4 ++-- safeclib/wcsncpy_s.c | 4 ++-- safeclib/wcsnlen_s.c | 2 +- safeclib/wmemmove_s.c | 2 +- safeclib/wmemset_s.c | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/safeclib/wcpcpy_s.c b/safeclib/wcpcpy_s.c index 0721d3d..ff9ecd6 100644 --- a/safeclib/wcpcpy_s.c +++ b/safeclib/wcpcpy_s.c @@ -91,7 +91,7 @@ wcpcpy_s(wchar_t* dest, rsize_t dmax, const wchar_t* src, errno_t *err) return NULL; } - if (dmax*sizeof(wchar_t) > RSIZE_MAX_STR) { + if (dmax > RSIZE_MAX_STR / sizeof(wchar_t)) { invoke_safe_str_constraint_handler("wcpcpy_s: dmax exceeds max", NULL, ESLEMAX); *err = RCNEGATE(ESLEMAX); diff --git a/safeclib/wcscat_s.c b/safeclib/wcscat_s.c index f682917..b3360fa 100644 --- a/safeclib/wcscat_s.c +++ b/safeclib/wcscat_s.c @@ -96,7 +96,7 @@ wcscat_s(wchar_t* dest, rsize_t dmax, const wchar_t* src) return RCNEGATE(ESZEROL); } - if (dmax*sizeof(wchar_t) > RSIZE_MAX_STR) { + if (dmax > RSIZE_MAX_STR / sizeof(wchar_t)) { invoke_safe_str_constraint_handler("wcscat_s: dmax exceeds max", NULL, ESLEMAX); return RCNEGATE(ESLEMAX); diff --git a/safeclib/wcscpy_s.c b/safeclib/wcscpy_s.c index bc0fe75..3fdc7af 100644 --- a/safeclib/wcscpy_s.c +++ b/safeclib/wcscpy_s.c @@ -84,7 +84,7 @@ wcscpy_s(wchar_t* dest, rsize_t dmax, const wchar_t* src) return RCNEGATE(ESZEROL); } - if (dmax*sizeof(wchar_t) > RSIZE_MAX_STR) { + if (dmax > RSIZE_MAX_STR / sizeof(wchar_t)) { invoke_safe_str_constraint_handler("wcscpy_s: dmax exceeds max", NULL, ESLEMAX); return RCNEGATE(ESLEMAX); diff --git a/safeclib/wcsncat_s.c b/safeclib/wcsncat_s.c index c15d991..e6845b5 100644 --- a/safeclib/wcsncat_s.c +++ b/safeclib/wcsncat_s.c @@ -94,7 +94,7 @@ wcsncat_s (wchar_t *dest, rsize_t dmax, const wchar_t *src, rsize_t slen) return RCNEGATE(ESNULLP); } - if (slen*sizeof(wchar_t) > RSIZE_MAX_STR) { + if (slen > RSIZE_MAX_STR / sizeof(wchar_t)) { invoke_safe_str_constraint_handler("wcsncat_s: slen exceeds max", NULL, ESLEMAX); return RCNEGATE(ESLEMAX); @@ -106,7 +106,7 @@ wcsncat_s (wchar_t *dest, rsize_t dmax, const wchar_t *src, rsize_t slen) return RCNEGATE(ESZEROL); } - if (dmax*sizeof(wchar_t) > RSIZE_MAX_STR) { + if (dmax > RSIZE_MAX_STR / sizeof(wchar_t)) { invoke_safe_str_constraint_handler("wcsncat_s: dmax exceeds max", NULL, ESLEMAX); return RCNEGATE(ESLEMAX); diff --git a/safeclib/wcsncpy_s.c b/safeclib/wcsncpy_s.c index eeefb4f..55174c8 100644 --- a/safeclib/wcsncpy_s.c +++ b/safeclib/wcsncpy_s.c @@ -99,7 +99,7 @@ wcsncpy_s(wchar_t* dest, rsize_t dmax, const wchar_t* src, rsize_t slen) return RCNEGATE(ESZEROL); } - if (dmax*sizeof(wchar_t) > RSIZE_MAX_STR) { + if (dmax > RSIZE_MAX_STR / sizeof(wchar_t)) { invoke_safe_str_constraint_handler("wcsncpy_s: dmax exceeds max", NULL, ESLEMAX); return RCNEGATE(ESLEMAX); @@ -121,7 +121,7 @@ wcsncpy_s(wchar_t* dest, rsize_t dmax, const wchar_t* src, rsize_t slen) return RCNEGATE(ESZEROL); } - if (slen*sizeof(wchar_t) > RSIZE_MAX_STR) { + if (slen > RSIZE_MAX_STR / sizeof(wchar_t)) { handle_wc_error(orig_dest, orig_dmax, "wcsncpy_s: slen exceeds max", ESLEMAX); return RCNEGATE(ESLEMAX); diff --git a/safeclib/wcsnlen_s.c b/safeclib/wcsnlen_s.c index e981e7f..b4fe891 100644 --- a/safeclib/wcsnlen_s.c +++ b/safeclib/wcsnlen_s.c @@ -69,7 +69,7 @@ wcsnlen_s (const wchar_t *dest, rsize_t dmax) return RCNEGATE(0); } - if (dmax*sizeof(wchar_t) > RSIZE_MAX_STR) { + if (dmax > RSIZE_MAX_STR / sizeof(wchar_t)) { invoke_safe_str_constraint_handler("wcsnlen_s: dmax exceeds max", NULL, ESLEMAX); return RCNEGATE(0); diff --git a/safeclib/wmemmove_s.c b/safeclib/wmemmove_s.c index e3e7af9..8a7e4a4 100644 --- a/safeclib/wmemmove_s.c +++ b/safeclib/wmemmove_s.c @@ -87,7 +87,7 @@ wmemmove_s(wchar_t* dest, rsize_t dmax, const wchar_t* src, size_t smax) return (RCNEGATE(ESZEROL)); } - if (dmax*sizeof(wchar_t) > RSIZE_MAX_MEM) { + if (dmax > RSIZE_MAX_MEM / sizeof(wchar_t)) { invoke_safe_mem_constraint_handler("wmemmove_s: dmax exceeds max", NULL, ESLEMAX); return (RCNEGATE(ESLEMAX)); diff --git a/safeclib/wmemset_s.c b/safeclib/wmemset_s.c index c0e5c93..615232b 100644 --- a/safeclib/wmemset_s.c +++ b/safeclib/wmemset_s.c @@ -66,7 +66,7 @@ wmemset_s (wchar_t *dest, wchar_t value, rsize_t len) return (RCNEGATE(ESZEROL)); } - if (len*sizeof(wchar_t) > RSIZE_MAX_MEM) { + if (len > RSIZE_MAX_MEM / sizeof(wchar_t)) { invoke_safe_mem_constraint_handler("wmemset_s: len exceeds max", NULL, ESLEMAX); return (RCNEGATE(ESLEMAX)); From 0d349b660c8da79756c4cd1857ad22dfccf56786 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Wed, 13 May 2026 12:43:39 +0300 Subject: [PATCH 2/7] safestringlib: strcmpfld_s: do not fill indicator on equal string Fill indicator only when difference is found to avoid improper management of edge conditions. Signed-off-by: Alexander Usyskin --- safeclib/strcmpfld_s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/safeclib/strcmpfld_s.c b/safeclib/strcmpfld_s.c index 9388938..f23653a 100644 --- a/safeclib/strcmpfld_s.c +++ b/safeclib/strcmpfld_s.c @@ -105,6 +105,7 @@ strcmpfld_s (const char *dest, rsize_t dmax, while (dmax) { if (*dest != *src) { + *indicator = *dest - *src; break; } @@ -113,7 +114,6 @@ strcmpfld_s (const char *dest, rsize_t dmax, dmax--; } - *indicator = *dest - *src; return (EOK); } EXPORT_SYMBOL(strcmpfld_s) From 80ae3c1e5a207827a33cc47134452cbdce987d44 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Wed, 13 May 2026 13:34:09 +0300 Subject: [PATCH 3/7] safestringlib: strcspn_s: check smax first in read Check for non-zero smax first to avoid improper management of edge conditions. Signed-off-by: Alexander Usyskin --- safeclib/strcspn_s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/safeclib/strcspn_s.c b/safeclib/strcspn_s.c index c0fbbae..53d3fdf 100644 --- a/safeclib/strcspn_s.c +++ b/safeclib/strcspn_s.c @@ -121,7 +121,7 @@ strcspn_s (const char *dest, rsize_t dmax, */ smax = slen; scan2 = src; - while (*scan2 && smax) { + while (smax && *scan2) { if (*dest == *scan2) { return RCNEGATE(EOK); From 5d2006a9fcabd4450b28552bcd7f9f23612b101b Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Thu, 14 May 2026 13:15:52 +0300 Subject: [PATCH 4/7] safestringlib: strremovews_s: check lower bound Check lower bound when removing trailing whitespaces to avoid improper management of edge conditions on string that consists of whitespaces only. Signed-off-by: Alexander Usyskin --- safeclib/strremovews_s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/safeclib/strremovews_s.c b/safeclib/strremovews_s.c index 1ad4ba4..c90411a 100644 --- a/safeclib/strremovews_s.c +++ b/safeclib/strremovews_s.c @@ -129,7 +129,7 @@ strremovews_s (char *dest, rsize_t dmax) * strip trailing whitespace */ dest = orig_end; - while ((*dest == ' ') || (*dest == '\t')) { + while ((dest >= orig_dest) && ((*dest == ' ') || (*dest == '\t'))) { *dest = '\0'; dest--; } From 70b0ceae1900e6238b6b616e4412b6d8f3bbf76b Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Thu, 14 May 2026 13:24:28 +0300 Subject: [PATCH 5/7] safestringlib: strcpyfldin_s: avoid read over slen If null-terminator is missed in source string before slen there can be improper management of edge conditions. Enforce slen limit on copy to avoid this. Signed-off-by: Alexander Usyskin --- safeclib/strcpyfldin_s.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/safeclib/strcpyfldin_s.c b/safeclib/strcpyfldin_s.c index e34d706..98f2fda 100644 --- a/safeclib/strcpyfldin_s.c +++ b/safeclib/strcpyfldin_s.c @@ -126,7 +126,7 @@ strcpyfldin_s (char *dest, rsize_t dmax, const char *src, rsize_t slen) if (dest < src) { overlap_bumper = src; - while (dmax > 0 && *src) { + while (dmax > 0 && slen && *src) { if (dest == overlap_bumper) { dmax = orig_dmax; @@ -142,13 +142,14 @@ strcpyfldin_s (char *dest, rsize_t dmax, const char *src, rsize_t slen) } dmax--; + slen--; *dest++ = *src++; } } else { overlap_bumper = dest; - while (dmax > 0 && *src) { + while (dmax > 0 && slen && *src) { if (src == overlap_bumper) { dmax = orig_dmax; @@ -164,6 +165,7 @@ strcpyfldin_s (char *dest, rsize_t dmax, const char *src, rsize_t slen) } dmax--; + slen--; *dest++ = *src++; } } From 6c80cca807062bc99c5060028d9bf629ce6f362a Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Wed, 20 May 2026 11:01:12 +0300 Subject: [PATCH 6/7] safestringlib: parse_format: fail on unrecognized modifier Fail parse_format on unrecognized modifier to prevent their exploitation. Make the parse_format static to avoid it usage outside of the file. Signed-off-by: Alexander Usyskin --- safeclib/snprintf_support.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/safeclib/snprintf_support.c b/safeclib/snprintf_support.c index d7a4b67..2bc9f0d 100644 --- a/safeclib/snprintf_support.c +++ b/safeclib/snprintf_support.c @@ -29,8 +29,10 @@ #define CHK_FORMAT(X,Y) (((X)==(Y))?1:0) - -unsigned int +/* + * Partial parser for sanity checks + */ +static unsigned int parse_format(const char *format, char pformatList[], unsigned int maxFormats) { unsigned int numFormats = 0; @@ -167,7 +169,7 @@ parse_format(const char *format, char pformatList[], unsigned int maxFormats) printf("failed to recognize format string ["); for (;start Date: Mon, 1 Jun 2026 09:15:14 +0300 Subject: [PATCH 7/7] safestringlib: version 1.3.0 Bump to version 1.3.0. Signed-off-by: Alexander Usyskin --- CHANGELOG.md | 24 ++++++++++++++++++++++++ include/safe_lib.h | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea5449f..707fc6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # safestringlib +## v1.3.0: + + - parse_format: fail on unrecognized modifier + - strcpyfldin_s: avoid read over slen + - strremovews_s: check lower bound + - strcspn_s: check smax first in read + - strcmpfld_s: do not fill indicator on equal string + - fix wchar bound checking + - avoid redefining RSIZE_MAX + - cmake: BUILD_OPT_DEFAULT type should be bool + - cmake: do not probe for C++ + - cmake: update minimal version to 3.15 + - unittests: do not crash on len > RSIZE_MAX_STR + - align str rsize with the mem rsize + - workflows: upgrade actions version + - workflows: set top level read permissions + - silence unused parameters warning in error handlers + - safeclib/strpbrk_s.c: check string boundaries + - add Cmake support for creating a Debian package + - remove makefile it does not work anymore + - requires cmake version 3.5 or higer + - workflows: add codeql testing + - add security policy file + ## v1.2.0: - unittests: add test counter to strisdigit_s and strismixed_s.c - fix out of bounds check in stris_xxx functions diff --git a/include/safe_lib.h b/include/safe_lib.h index dae884d..a7d6fa5 100644 --- a/include/safe_lib.h +++ b/include/safe_lib.h @@ -15,9 +15,9 @@ extern "C" { /* Define safe_lib version number */ #define SAFEC_VERSION_MAJOR 1 -#define SAFEC_VERSION_MINOR 2 +#define SAFEC_VERSION_MINOR 3 #define SAFEC_VERSION_PATCH 0 -#define SAFEC_VERSION_STRING "1.2.0" +#define SAFEC_VERSION_STRING "1.3.0" #define SAFEC_VERSION_NUM(a,b,c) (((a) << 16L) | ((b) << 8) | (c)) #define SAFEC_VERSION \