From daef585f196e2fec1f541d553193383a02e7655e Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 23 Apr 2026 18:20:15 +0200 Subject: [PATCH 01/16] CVE-2026-4480/CVE-2026-4408: lib/util: inline string_sub2() into string_sub() the only caller This will simplify further changes. BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Signed-off-by: Stefan Metzmacher Reviewed-by: Douglas Bagnall --- lib/util/substitute.c | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/lib/util/substitute.c b/lib/util/substitute.c index b7b5588da863..26362ca77b2c 100644 --- a/lib/util/substitute.c +++ b/lib/util/substitute.c @@ -47,10 +47,9 @@ use of len==0 which was for no length checks to be done. **/ -static void string_sub2(char *s,const char *pattern, const char *insert, size_t len, - bool remove_unsafe_characters, bool replace_once, - bool allow_trailing_dollar) +void string_sub(char *s, const char *pattern, const char *insert, size_t len) { + bool remove_unsafe_characters = true; char *p; size_t ls, lp, li, i; @@ -79,13 +78,6 @@ static void string_sub2(char *s,const char *pattern, const char *insert, size_t for (i=0;i Date: Thu, 23 Apr 2026 18:20:15 +0200 Subject: [PATCH 02/16] CVE-2026-4480/CVE-2026-4408: lib/util: remove unused talloc_strdup(insert) from talloc_string_sub2() The insert string is not modified, so we do not need to copy it. This will simplify further changes. Review with: git show --patience BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Signed-off-by: Stefan Metzmacher --- lib/util/substitute.c | 57 +++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/lib/util/substitute.c b/lib/util/substitute.c index 26362ca77b2c..4a0c58ab3a7f 100644 --- a/lib/util/substitute.c +++ b/lib/util/substitute.c @@ -157,7 +157,7 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, bool replace_once, bool allow_trailing_dollar) { - char *p, *in; + char *p; char *s; char *string; ssize_t ls,lp,li,ld, i; @@ -175,22 +175,32 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, s = string; - in = talloc_strdup(mem_ctx, insert); - if (!in) { - DEBUG(0, ("talloc_string_sub2: ENOMEM\n")); - talloc_free(string); - return NULL; - } ls = (ssize_t)strlen(s); lp = (ssize_t)strlen(pattern); li = (ssize_t)strlen(insert); ld = li - lp; - for (i=0;i 0) { + int offset = PTR_DIFF(s,string); + string = (char *)talloc_realloc_size(mem_ctx, string, + ls + ld + 1); + if (!string) { + DEBUG(0, ("talloc_string_sub: out of " + "memory!\n")); + return NULL; + } + p = string + offset + (p - s); + } + if (li != lp) { + memmove(p+li,p+lp,strlen(p+lp)+1); + } + for (i=0; i 0) { - int offset = PTR_DIFF(s,string); - string = (char *)talloc_realloc_size(mem_ctx, string, - ls + ld + 1); - if (!string) { - DEBUG(0, ("talloc_string_sub: out of " - "memory!\n")); - TALLOC_FREE(in); - return NULL; } - p = string + offset + (p - s); - } - if (li != lp) { - memmove(p+li,p+lp,strlen(p+lp)+1); + + p[i] = insert[i]; } - memcpy(p, in, li); s = p + li; ls += ld; @@ -239,7 +233,6 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, break; } } - TALLOC_FREE(in); return string; } -- 2.43.0 From 3b0e15b84d55e46fa97348637f90128abbc16dbd Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 23 Apr 2026 18:20:15 +0200 Subject: [PATCH 03/16] CVE-2026-4480/CVE-2026-4408: lib/util: factor out a mask_unsafe_character() helper function This moves the logic into a single place and makes if more flexible to be used with more values than STRING_SUB_UNSAFE_CHARACTERS. BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Signed-off-by: Stefan Metzmacher --- lib/util/substitute.c | 109 +++++++++++++++++++++--------------------- lib/util/substitute.h | 6 ++- 2 files changed, 60 insertions(+), 55 deletions(-) diff --git a/lib/util/substitute.c b/lib/util/substitute.c index 4a0c58ab3a7f..b9fe32e993ec 100644 --- a/lib/util/substitute.c +++ b/lib/util/substitute.c @@ -35,6 +35,33 @@ * @brief Substitute utilities. **/ +static inline +char mask_unsafe_character(char in, + bool is_last, + bool allow_trailing_dollar, + const char *unsafe_characters, + char safe_out) +{ + const char *unsafe = NULL; + + if (unsafe_characters == NULL) { + return in; + } + + /* allow a trailing $ (as in machine accounts) */ + if (allow_trailing_dollar && is_last && in == '$') { + return in; + } + + unsafe = strchr(unsafe_characters, in); + if (unsafe != NULL) { + return safe_out; + } + + /* ok */ + return in; +} + /** Substitute a string for a pattern in another string. Make sure there is enough room! @@ -42,14 +69,16 @@ This routine looks for pattern in s and replaces it with insert. It may do multiple replacements or just one. - Any of " ; ' $ or ` in the insert string are replaced with _ + Any of STRING_SUB_UNSAFE_CHARACTERS in the insert string are replaced with _ + if len==0 then the string cannot be extended. This is different from the old use of len==0 which was for no length checks to be done. **/ void string_sub(char *s, const char *pattern, const char *insert, size_t len) { - bool remove_unsafe_characters = true; + const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS; + char safe_character = '_'; char *p; size_t ls, lp, li, i; @@ -76,26 +105,18 @@ void string_sub(char *s, const char *pattern, const char *insert, size_t len) memmove(p+li,p+lp,strlen(p+lp)+1); } for (i=0;i +#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%\r\n" + /** Substitute a string for a pattern in another string. Make sure there is enough room! @@ -33,7 +35,9 @@ This routine looks for pattern in s and replaces it with insert. It may do multiple replacements. - Any of " ; ' $ or ` in the insert string are replaced with _ + Any of STRING_SUB_UNSAFE_CHARACTERS (see above) in the + insert string are replaced with _ + if len==0 then the string cannot be extended. This is different from the old use of len==0 which was for no length checks to be done. **/ -- 2.43.0 From 72ded34891e6009fdbf33dfb766c7fc4de65f084 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 30 Apr 2026 14:48:26 +0200 Subject: [PATCH 04/16] CVE-2026-4480/CVE-2026-4408: lib/util: split out realloc_string_sub_raw() This will allow realloc_string_sub2() to use it in order to have the logic in one place only. And it will also allow adjacted callers to be more flexible. BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Signed-off-by: Stefan Metzmacher --- lib/util/substitute.c | 85 ++++++++++++++++++++++++++++++------------- lib/util/substitute.h | 18 +++++++++ 2 files changed, 78 insertions(+), 25 deletions(-) diff --git a/lib/util/substitute.c b/lib/util/substitute.c index b9fe32e993ec..465aea866055 100644 --- a/lib/util/substitute.c +++ b/lib/util/substitute.c @@ -171,32 +171,24 @@ _PUBLIC_ void all_string_sub(char *s,const char *pattern,const char *insert, siz * talloc version of string_sub2. */ -char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, - const char *pattern, - const char *insert, - bool remove_unsafe_characters, - bool replace_once, - bool allow_trailing_dollar) +bool realloc_string_sub_raw(char **_string, + const char *pattern, + const char *insert, + bool replace_once, + bool allow_trailing_dollar, + const char *unsafe_characters, + char safe_character) { - const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS; - const char safe_character = '_'; - char *p = NULL, + char *p = NULL; char *s = NULL; char *string = NULL; ssize_t ls,lp,li,ld, i; - if (!insert || !pattern || !*pattern || !src) { - return NULL; - } - - string = talloc_strdup(mem_ctx, src); - if (string == NULL) { - DEBUG(0, ("talloc_string_sub2: " - "talloc_strdup failed\n")); - return NULL; + if (!insert || !pattern || !*pattern || !_string|| !*_string) { + return false; } - s = string; + s = string = *_string; ls = (ssize_t)strlen(s); lp = (ssize_t)strlen(pattern); @@ -205,14 +197,13 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, while ((p = strstr_m(s,pattern))) { if (ld > 0) { - int offset = PTR_DIFF(s,string); - string = (char *)talloc_realloc_size(mem_ctx, string, - ls + ld + 1); + ptrdiff_t offset = PTR_DIFF(s,string); + string = talloc_realloc(NULL, string, char, ls + ld + 1); if (!string) { - DEBUG(0, ("talloc_string_sub: out of " - "memory!\n")); - return NULL; + DBG_ERR("out of memory(realloc)!\n"); + return false; } + *_string = string; p = string + offset + (p - s); } if (li != lp) { @@ -234,6 +225,50 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, break; } } + return true; +} + +char *talloc_string_sub2(TALLOC_CTX *mem_ctx, + const char *src, + const char *pattern, + const char *insert, + bool remove_unsafe_characters, + bool replace_once, + bool allow_trailing_dollar) +{ + const char *unsafe_characters = NULL; + char safe_character = '\0'; + char *string = NULL; + bool ok; + + if (!insert || !pattern || !*pattern || !src) { + return NULL; + } + + if (remove_unsafe_characters) { + unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS; + safe_character = '_'; + } + + string = talloc_strdup(mem_ctx, src); + if (string == NULL) { + DBG_ERR("out of memory, talloc_strdup(src)!\n"); + return NULL; + } + + ok = realloc_string_sub_raw(&string, + pattern, + insert, + replace_once, + allow_trailing_dollar, + unsafe_characters, + safe_character); + if (!ok) { + TALLOC_FREE(string); + DBG_ERR("out of memory, realloc_string_sub_raw()!\n"); + return NULL; + } + return string; } diff --git a/lib/util/substitute.h b/lib/util/substitute.h index e1a82859daca..041a649fd181 100644 --- a/lib/util/substitute.h +++ b/lib/util/substitute.h @@ -51,6 +51,24 @@ void string_sub(char *s,const char *pattern, const char *insert, size_t len); **/ void all_string_sub(char *s,const char *pattern,const char *insert, size_t len); +/* + * If unsafe_characters is NULL all characters are allowed, + * if unsafe_characters is not NULL all characters caught + * by iscntrl() are also replaced by safe_character. + * + * *_string might be reallocated! + * + * On error *_string may still be reallocated and + * may contain partial replacements. + */ +bool realloc_string_sub_raw(char **_string, + const char *pattern, + const char *insert, + bool replace_once, + bool allow_trailing_dollar, + const char *unsafe_characters, + char safe_character); + char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, const char *pattern, const char *insert, -- 2.43.0 From d857e037292681fcefdc35c24d1a1154f7ee2ced Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 6 May 2026 17:23:39 +0200 Subject: [PATCH 05/16] CVE-2026-4480/CVE-2026-4408: s3:lib: fix potential memory leak in talloc_sub_basic() This makes the code easier to understand... BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Signed-off-by: Stefan Metzmacher --- source3/lib/substitute.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source3/lib/substitute.c b/source3/lib/substitute.c index 40eb15aee04b..6ef5d2b32889 100644 --- a/source3/lib/substitute.c +++ b/source3/lib/substitute.c @@ -317,6 +317,7 @@ char *talloc_sub_basic(TALLOC_CTX *mem_ctx, } tmp_ctx = talloc_stackframe(); + talloc_steal(tmp_ctx, a_string); for (s = a_string; (p = strchr_m(s, '%')); s = a_string + (p - b)) { @@ -479,7 +480,7 @@ error: done: TALLOC_FREE(tmp_ctx); - return a_string; + return talloc_move(mem_ctx, &a_string); } /**************************************************************************** -- 2.43.0 From 1a195101bb8a0e458ee348fbe81afeb2367a2046 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 23 Apr 2026 21:11:27 +0200 Subject: [PATCH 06/16] CVE-2026-4480/CVE-2026-4408: s3:lib: let realloc_string_sub2() use realloc_string_sub_raw() We don't need this logic more than once! But we leave the strange calling convention of realloc_string_sub2(), where the caller it not allowed to use the passed pointer when NULL is returned... BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Signed-off-by: Stefan Metzmacher --- source3/lib/substitute_generic.c | 81 ++++++++++---------------------- 1 file changed, 24 insertions(+), 57 deletions(-) diff --git a/source3/lib/substitute_generic.c b/source3/lib/substitute_generic.c index 26c5ee761f8b..e0639f04eb8e 100644 --- a/source3/lib/substitute_generic.c +++ b/source3/lib/substitute_generic.c @@ -37,71 +37,38 @@ char *realloc_string_sub2(char *string, bool remove_unsafe_characters, bool allow_trailing_dollar) { - char *p, *in; - char *s; - ssize_t ls,lp,li,ld, i; + const char *unsafe_characters = NULL; + char safe_character = '\0'; + bool ok; if (!insert || !pattern || !*pattern || !string || !*string) return NULL; - s = string; + if (remove_unsafe_characters) { + unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS; + safe_character = '_'; + } - in = talloc_strdup(talloc_tos(), insert); - if (!in) { - DEBUG(0, ("realloc_string_sub: out of memory!\n")); + ok = realloc_string_sub_raw(&string, + pattern, + insert, + false, /* replace_once */ + allow_trailing_dollar, + unsafe_characters, + safe_character); + if (!ok) { + DBG_ERR("out of memory, realloc_string_sub_raw()!\n"); + /* + * The calling convention of realloc_string_sub2() + * is very strange regarding stale string pointers. + * + * It is assumed the given string was allocated + * on talloc_tos(), so we just don't touch + * it at all here... + */ return NULL; } - ls = (ssize_t)strlen(s); - lp = (ssize_t)strlen(pattern); - li = (ssize_t)strlen(insert); - ld = li - lp; - for (i=0;i 0) { - int offset = PTR_DIFF(s,string); - string = talloc_realloc(NULL, string, char, ls + ld + 1); - if (!string) { - DEBUG(0, ("realloc_string_sub: " - "out of memory!\n")); - talloc_free(in); - return NULL; - } - p = string + offset + (p - s); - } - if (li != lp) { - memmove(p+li,p+lp,strlen(p+lp)+1); - } - memcpy(p, in, li); - s = p + li; - ls += ld; - } - talloc_free(in); return string; } -- 2.43.0 From 5c478f313f1158fd9faf9c843a1eacb234321b21 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 23 Apr 2026 18:21:08 +0200 Subject: [PATCH 07/16] CVE-2026-4480/CVE-2026-4408: lib/util: let mask_unsafe_character() check all control characters There's no reason to mask only \r and \n. BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Signed-off-by: Stefan Metzmacher --- lib/util/substitute.c | 8 +++++++- lib/util/substitute.h | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/util/substitute.c b/lib/util/substitute.c index 465aea866055..30989927da72 100644 --- a/lib/util/substitute.c +++ b/lib/util/substitute.c @@ -22,6 +22,7 @@ */ #include "replace.h" +#include "system/locale.h" #include "debug.h" #ifndef SAMBA_UTIL_CORE_ONLY #include "charset/charset.h" @@ -53,6 +54,10 @@ char mask_unsafe_character(char in, return in; } + if (iscntrl(in)) { + return safe_out; + } + unsafe = strchr(unsafe_characters, in); if (unsafe != NULL) { return safe_out; @@ -69,7 +74,8 @@ char mask_unsafe_character(char in, This routine looks for pattern in s and replaces it with insert. It may do multiple replacements or just one. - Any of STRING_SUB_UNSAFE_CHARACTERS in the insert string are replaced with _ + Any of STRING_SUB_UNSAFE_CHARACTERS and any character + caught by calling iscntrl() in the insert string are replaced with _ if len==0 then the string cannot be extended. This is different from the old use of len==0 which was for no length checks to be done. diff --git a/lib/util/substitute.h b/lib/util/substitute.h index 041a649fd181..b183d864671a 100644 --- a/lib/util/substitute.h +++ b/lib/util/substitute.h @@ -26,7 +26,7 @@ #include -#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%\r\n" +#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%" /** Substitute a string for a pattern in another string. Make sure there is @@ -35,8 +35,8 @@ This routine looks for pattern in s and replaces it with insert. It may do multiple replacements. - Any of STRING_SUB_UNSAFE_CHARACTERS (see above) in the - insert string are replaced with _ + Any of STRING_SUB_UNSAFE_CHARACTERS (see above) and any character + caught by calling iscntrl() in the insert string are replaced with _ if len==0 then the string cannot be extended. This is different from the old use of len==0 which was for no length checks to be done. -- 2.43.0 From 2379ed29c9f0911f2bb332c45c6073d40271c715 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 23 Apr 2026 18:21:08 +0200 Subject: [PATCH 08/16] CVE-2026-4480/CVE-2026-4408: lib/util: add more unsafe characters to STRING_SUB_UNSAFE_CHARACTERS |&<> are unsafe characters for shell processing. BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Signed-off-by: Stefan Metzmacher --- lib/util/substitute.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util/substitute.h b/lib/util/substitute.h index b183d864671a..41f56c73ba2c 100644 --- a/lib/util/substitute.h +++ b/lib/util/substitute.h @@ -26,7 +26,7 @@ #include -#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%" +#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%|&<>" /** Substitute a string for a pattern in another string. Make sure there is -- 2.43.0 From 3bb8bcad52cf8cb934d4aaa4a868187cdd3d7d68 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 May 2026 22:33:32 +0200 Subject: [PATCH 09/16] CVE-2026-4480/CVE-2026-4408: lib/util: let log_escape() make use of iscntrl() using iscntrl() also handles 0x7F (DEL). BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Signed-off-by: Stefan Metzmacher --- lib/util/util_str_escape.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util/util_str_escape.c b/lib/util/util_str_escape.c index 8f1f34912ee6..690bc568cb37 100644 --- a/lib/util/util_str_escape.c +++ b/lib/util/util_str_escape.c @@ -28,7 +28,7 @@ */ static size_t encoded_length(unsigned char c) { - if (c != '\\' && c > 0x1F) { + if (c != '\\' && !iscntrl(c)) { return 1; } else { switch (c) { @@ -79,7 +79,7 @@ char *log_escape(TALLOC_CTX *frame, const char *in) c = in; e = encoded; while (*c) { - if (*c != '\\' && (unsigned char)(*c) > 0x1F) { + if (*c != '\\' && !iscntrl((unsigned char)(*c))) { *e++ = *c++; } else { switch (*c) { -- 2.43.0 From 8482e3227df44609128b9f7a700dca52db61ad94 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 8 May 2026 12:59:14 +1200 Subject: [PATCH 10/16] CVE-2026-4480/CVE-2026-4408: lib/util: add talloc_escape_shell_string() This is basically a copy of escape_shell_string(), but it uses talloc instead and also allows the caller to escape caracters caught by isspace(), here only ' ' and '\t' matter as they are in INCLUDE_LIST. BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Signed-off-by: Stefan Metzmacher --- lib/util/util_str_escape.c | 188 ++++++++++++++++++++++++++++++++++++- lib/util/util_str_escape.h | 8 ++ 2 files changed, 195 insertions(+), 1 deletion(-) diff --git a/lib/util/util_str_escape.c b/lib/util/util_str_escape.c index 690bc568cb37..b3754bf09075 100644 --- a/lib/util/util_str_escape.c +++ b/lib/util/util_str_escape.c @@ -18,9 +18,12 @@ */ #include "replace.h" +#include "system/locale.h" #include "lib/util/debug.h" #include "lib/util/util_str_escape.h" - +#ifndef SAMBA_UTIL_CORE_ONLY +#include "lib/util/charset/charset.h" +#endif /* * Calculate the encoded length of a character for log_escape @@ -125,3 +128,186 @@ char *log_escape(TALLOC_CTX *frame, const char *in) *e = '\0'; return encoded; } + + +#ifndef SAMBA_UTIL_CORE_ONLY + +/******************************************************************* + Add a shell escape character '\' to any character not in a known list + of characters. UNIX charset format. +*******************************************************************/ + +#define INCLUDE_LIST "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_/ \t.," +#define INSIDE_DQUOTE_LIST "$`\n\"\\" + +char *talloc_escape_shell_string(TALLOC_CTX *mem_ctx, + const char *src, + bool escape_space) +{ + size_t srclen = strlen(src); + char *ret = talloc_array(mem_ctx, char, (srclen * 2) + 1); + char *dest = ret; + bool in_s_quote = false; + bool in_d_quote = false; + bool next_escaped = false; + + if (!ret) { + return NULL; + } + + while (*src) { + size_t c_size; + codepoint_t c = next_codepoint(src, &c_size); + + if (c == INVALID_CODEPOINT) { + TALLOC_FREE(ret); + return NULL; + } + + if (c_size > 1) { + memcpy(dest, src, c_size); + src += c_size; + dest += c_size; + next_escaped = false; + continue; + } + + /* + * Deal with backslash escaped state. + * This only lasts for one character. + */ + + if (next_escaped) { + *dest++ = *src++; + next_escaped = false; + continue; + } + + /* + * Deal with single quote state. The + * only thing we care about is exiting + * this state. + */ + + if (in_s_quote) { + if (*src == '\'') { + in_s_quote = false; + } + *dest++ = *src++; + continue; + } + + /* + * Deal with double quote state. The most + * complex state. We must cope with \, meaning + * possibly escape next char (depending what it + * is), ", meaning exit this state, and possibly + * add an \ escape to any unprotected character + * (listed in INSIDE_DQUOTE_LIST). + */ + + if (in_d_quote) { + if (*src == '\\') { + /* + * Next character might be escaped. + * We have to peek. Inside double + * quotes only INSIDE_DQUOTE_LIST + * characters are escaped by a \. + */ + + char nextchar; + + c = next_codepoint(&src[1], &c_size); + if (c == INVALID_CODEPOINT) { + TALLOC_FREE(ret); + return NULL; + } + if (c_size > 1) { + /* + * Don't escape the next char. + * Just copy the \. + */ + *dest++ = *src++; + continue; + } + + nextchar = src[1]; + + if (nextchar && strchr(INSIDE_DQUOTE_LIST, + (int)nextchar)) { + next_escaped = true; + } + *dest++ = *src++; + continue; + } + + if (*src == '\"') { + /* Exit double quote state. */ + in_d_quote = false; + *dest++ = *src++; + continue; + } + + /* + * We know the character isn't \ or ", + * so escape it if it's any of the other + * possible unprotected characters. + */ + + if (strchr(INSIDE_DQUOTE_LIST, (int)*src)) { + *dest++ = '\\'; + } + *dest++ = *src++; + continue; + } + + /* + * From here to the end of the loop we're + * not in the single or double quote state. + */ + + if (*src == '\\') { + /* Next character must be escaped. */ + next_escaped = true; + *dest++ = *src++; + continue; + } + + if (*src == '\'') { + /* Go into single quote state. */ + in_s_quote = true; + *dest++ = *src++; + continue; + } + + if (*src == '\"') { + /* Go into double quote state. */ + in_d_quote = true; + *dest++ = *src++; + continue; + } + + if (escape_space && isspace((int)*src)) { + /* + * tab and space are in INCLUDE_LIST and would + * not be escaped if escape_space is + * false. Other whitespace is always escaped. + */ + *dest++ = '\\'; + *dest++ = *src++; + continue; + } + + /* Check if we need to escape the character. */ + if (!strchr(INCLUDE_LIST, (int)*src)) { + *dest++ = '\\'; + } + *dest++ = *src++; + } + *dest++ = '\0'; + return ret; +} +#undef INCLUDE_LIST +#undef INSIDE_DQUOTE_LIST + +#endif /* SAMBA_UTIL_CORE_ONLY */ diff --git a/lib/util/util_str_escape.h b/lib/util/util_str_escape.h index 0b4c5964c144..285394988d8b 100644 --- a/lib/util/util_str_escape.h +++ b/lib/util/util_str_escape.h @@ -20,8 +20,16 @@ #ifndef _SAMBA_UTIL_STR_ESCAPE_H #define _SAMBA_UTIL_STR_ESCAPE_H +#include #include char *log_escape(TALLOC_CTX *frame, const char *in); + +#ifndef SAMBA_UTIL_CORE_ONLY +char *talloc_escape_shell_string(TALLOC_CTX *mem_ctx, + const char *src, + bool escape_space); +#endif /* ! SAMBA_UTIL_CORE_ONLY */ + #endif -- 2.43.0 From 50c7ae0c87780a613ebb8635185a70d99b20c738 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 7 May 2026 18:10:50 +0200 Subject: [PATCH 11/16] CVE-2026-4480/CVE-2026-4408: lib/util: add talloc_string_sub_unsafe() helper This is the basic helper function for the security problems. It tries to autodetect how the unsafe (client controlled value) and be masked, single quotes and/or shell escaped. BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Pair-Programmed-With: Douglas Bagnall Signed-off-by: Stefan Metzmacher --- lib/util/substitute.c | 232 +++++++++++++++++++++++++++++++++++++++++ lib/util/substitute.h | 13 +++ lib/util/wscript_build | 1 + 3 files changed, 246 insertions(+) diff --git a/lib/util/substitute.c b/lib/util/substitute.c index 30989927da72..7a5c3effe981 100644 --- a/lib/util/substitute.c +++ b/lib/util/substitute.c @@ -25,6 +25,9 @@ #include "system/locale.h" #include "debug.h" #ifndef SAMBA_UTIL_CORE_ONLY +#include "lib/util/fault.h" +#include "lib/util/talloc_stack.h" +#include "lib/util/util_str_escape.h" #include "charset/charset.h" #else #include "charset_compat.h" @@ -297,3 +300,232 @@ char *talloc_all_string_sub(TALLOC_CTX *ctx, return talloc_string_sub2(ctx, src, pattern, insert, false, false, false); } + +#ifndef SAMBA_UTIL_CORE_ONLY + +char *talloc_string_sub_unsafe(TALLOC_CTX *mem_ctx, + const char *orig_cmd, + char variable_char, + const char *unsafe_value, + const char *unsafe_characters, + char safe_character, + bool *_modified, + bool *_masked, + bool *_escaped) +{ + TALLOC_CTX *frame = talloc_stackframe(); + const char variable[3] = + { '%', variable_char, '\0' }; + const char variable_s_quoted[5] = + { '\'', '%', variable_char, '\'', '\0' }; + const char variable_d_quoted[5] = + { '"', '%', variable_char, '"', '\0' }; + char *cmd = NULL; + char *masked_value = NULL; + char *quoted_value = NULL; + bool has_s_quotes; + bool has_d_quotes; + bool has_variable; + bool has_variable_s_quoted; + bool has_variable_d_quoted; + bool modified = false; + bool masked = false; + bool escaped = false; + bool ok; + + /* + * The unsafe_characters argument should contain + * single and double quotes. + * Otherwise We can't safely handle this. + */ + SMB_ASSERT(unsafe_characters != NULL); + SMB_ASSERT(strchr(unsafe_characters, '\'') == NULL); + SMB_ASSERT(strchr(unsafe_characters, '"') == NULL); + + cmd = talloc_strdup(mem_ctx, orig_cmd); + if (cmd == NULL) { + TALLOC_FREE(frame); + return NULL; + } + cmd = talloc_steal(frame, cmd); + + has_variable = strstr(orig_cmd, variable) != NULL; + if (!has_variable) { + /* + * Nothing to do... + */ + goto done; + } + modified = true; + + /* + * Replace all unsafe characters as well as control + * characters. + * + * And don't allow option injected like + * + * '-h value' + * '--help value' + */ + masked_value = talloc_strdup(frame, unsafe_value); + if (masked_value == NULL) { + goto nomem; + } + ok = realloc_string_sub_raw(&masked_value, + variable, + variable, + false, /* replace_once */ + false, /* allow_trailing_dollar */ + unsafe_characters, + safe_character); + if (!ok) { + goto nomem; + } + if (masked_value[0] == '-') { + masked_value[0] = safe_character; + } + masked = strcmp(masked_value, unsafe_value) != 0; + +retry: + + has_s_quotes = strchr(cmd, '\'') != NULL; + has_d_quotes = strchr(cmd, '"') != NULL; + has_variable = strstr(orig_cmd, variable) != NULL; + has_variable_s_quoted = strstr(cmd, variable_s_quoted) != NULL; + has_variable_d_quoted = strstr(cmd, variable_d_quoted) != NULL; + + if (has_variable_s_quoted) { + /* + * In smb.conf we have something like + * + * some script = /usr/bin/script '%u' + * + * It is safe to replace '%u' (or '%J' etc, depending + * on variable_char) with '' if + * masked_value does not contain single quotes. We + * have checked that. + */ + + if (quoted_value == NULL) { + quoted_value = talloc_asprintf(frame, "'%s'", + masked_value); + if (quoted_value == NULL) { + goto nomem; + } + } + + ok = realloc_string_sub_raw(&cmd, + variable_s_quoted, + quoted_value, + false, /* replace_once */ + false, /* allow_trailing_dollar */ + NULL, /* unsafe_characters */ + '\0'); /* safe_character */ + if (!ok) { + goto nomem; + } + } + + if (has_variable_d_quoted && !has_s_quotes) { + /* + * replace the "%u" + * + * some script = /usr/bin/script "%u" + * + * with '%u' and try the '%u' -> 'variable' substitution + * again. + */ + + ok = realloc_string_sub_raw(&cmd, + variable_d_quoted, + variable_s_quoted, + false, /* replace_once */ + false, /* allow_trailing_dollar */ + NULL, /* unsafe_characters */ + '\0'); /* safe_character */ + if (!ok) { + goto nomem; + } + + goto retry; + } + + if (has_variable && !has_s_quotes && !has_d_quotes) { + /* + * In this case: + * + * some script = /usr/bin/script %u + * + * we can safely substitute %u -> '%u' and try the + * single quote test again. + */ + + ok = realloc_string_sub_raw(&cmd, + variable, + variable_s_quoted, + false, /* replace_once */ + false, /* allow_trailing_dollar */ + NULL, /* unsafe_characters */ + '\0'); /* safe_character */ + if (!ok) { + goto nomem; + } + + goto retry; + } + + if (has_variable) { + const bool escape_space = true; + char *escaped_value = NULL; + + /* + * There are single or double quotes, but not tightly + * bound around a %u. + * + * Or there's a mix of single and double quotes. + * + * There is no universal answer, but we escape variable to that + * it will remain a single token if it ends up outside quotes. + * + * The caller should warn about this + * and give the admin a hind to fix it, + * as the value might be shell escaped and + * quoted at the same time. + */ + escaped = true; + + escaped_value = talloc_escape_shell_string(mem_ctx, + masked_value, + escape_space); + if (escaped_value == NULL) { + goto nomem; + } + + ok = realloc_string_sub_raw(&cmd, + variable, + escaped_value, + false, /* replace_once */ + false, /* allow_trailing_dollar */ + NULL, /* unsafe_characters */ + '\0'); /* safe_character */ + if (!ok) { + goto nomem; + } + } + +done: + *_modified = modified; + *_masked = masked; + *_escaped = escaped; + cmd = talloc_steal(mem_ctx, cmd); + TALLOC_FREE(frame); + return cmd; + +nomem: + *_modified = false; + *_masked = false; + *_escaped = false; + TALLOC_FREE(frame); + return NULL; +} +#endif /* ! SAMBA_UTIL_CORE_ONLY */ diff --git a/lib/util/substitute.h b/lib/util/substitute.h index 41f56c73ba2c..c119dc3764eb 100644 --- a/lib/util/substitute.h +++ b/lib/util/substitute.h @@ -83,4 +83,17 @@ char *talloc_all_string_sub(TALLOC_CTX *ctx, const char *src, const char *pattern, const char *insert); + +#ifndef SAMBA_UTIL_CORE_ONLY +char *talloc_string_sub_unsafe(TALLOC_CTX *mem_ctx, + const char *orig_cmd, + char variable_char, + const char *unsafe_value, + const char *unsafe_characters, + char safe_character, + bool *_modified, + bool *_masked, + bool *_escaped); +#endif /* ! SAMBA_UTIL_CORE_ONLY */ + #endif /* _SAMBA_SUBSTITUTE_H_ */ diff --git a/lib/util/wscript_build b/lib/util/wscript_build index 3e96c6caf5f8..14c93749360f 100644 --- a/lib/util/wscript_build +++ b/lib/util/wscript_build @@ -117,6 +117,7 @@ bld.SAMBA_SUBSYSTEM('samba-util-core', pthread strv tini + util_str_escape smb_strtox smb-panic gnutls -- 2.43.0 From 85f15d0a893f6263135f33b0441afaa0d2beca30 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Sun, 15 Mar 2026 19:15:14 +0100 Subject: [PATCH 12/16] CVE-2026-4480: s3:printing: Shell-sanitize jobname passed as %J to "print command" Fix an unauthenticated remote code execution vulnerability with printing set to anything *but* cups and iprint, for example "lprng", so that "print command" is executed upon job submission. If the client-controlled job name is handed to the "print command" via "%J", rpcd_spoolssd passes this to the shell without escaping critical characters. BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 Signed-off-by: Stefan Metzmacher --- source3/printing/print_generic.c | 80 ++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/source3/printing/print_generic.c b/source3/printing/print_generic.c index 855de4ca1f36..a53efe1d2af6 100644 --- a/source3/printing/print_generic.c +++ b/source3/printing/print_generic.c @@ -19,6 +19,7 @@ #include "includes.h" #include "lib/util/util_file.h" +#include "lib/util/util_str_escape.h" #include "printing.h" #include "smbd/proto.h" #include "source3/lib/substitute.h" @@ -207,6 +208,51 @@ static int generic_queue_get(const char *printer_name, return qcount; } +static const char *replace_print_cmd_J(TALLOC_CTX *mem_ctx, + const char *orig_cmd, + const char *unsafe_jobname) +{ + char *cmd = NULL; + bool modified = false; + bool masked = false; + bool escaped = false; + + /* + * with '_'. + * + * Then it replaces %u with an single quoted + * and/or shell escaped version of the masked username. + */ + +#define JOBNAME_UNSAFE_CHARACTERS \ + STRING_SUB_UNSAFE_CHARACTERS "!/\\{}:" + + cmd = talloc_string_sub_unsafe(mem_ctx, + orig_cmd, + 'J', + unsafe_jobname, + JOBNAME_UNSAFE_CHARACTERS, + '_', + &modified, + &masked, + &escaped); + if (cmd == NULL) { + return NULL; + } + + /* + * Now warn about unexpected values + */ + if (escaped) { + // TODO printer/sharename??? + D_WARNING("CVE-2026-4480: " + "strange quoting in 'print command': %s\n", + orig_cmd); + } + + return cmd; +} + /**************************************************************************** Submit a file for printing - called from print_job_end() ****************************************************************************/ @@ -222,7 +268,7 @@ static int generic_job_submit(int snum, struct printjob *pjob, char *print_directory = NULL; char *wd = NULL; char *p = NULL; - char *jobname = NULL; + const char *print_cmd = NULL; TALLOC_CTX *ctx = talloc_tos(); fstring job_page_count, job_size; print_queue_struct *q = NULL; @@ -255,24 +301,21 @@ static int generic_job_submit(int snum, struct printjob *pjob, return -1; } - jobname = talloc_strdup(ctx, pjob->jobname); - if (!jobname) { - ret = -1; - goto out; - } - jobname = talloc_string_sub(ctx, jobname, "'", "_"); - if (!jobname) { - ret = -1; - goto out; + print_cmd = lp_print_command(snum); + if (print_cmd != NULL) { + print_cmd = replace_print_cmd_J(ctx, print_cmd, pjob->jobname); + if (!print_cmd) { + ret = -1; + goto out; + } } fstr_sprintf(job_page_count, "%d", pjob->page_count); fstr_sprintf(job_size, "%zu", pjob->size); /* send it to the system spooler */ ret = print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True, - lp_print_command(snum), NULL, + print_cmd, NULL, "%s", p, - "%J", jobname, "%f", p, "%z", job_size, "%c", job_page_count, @@ -293,17 +336,26 @@ static int generic_job_submit(int snum, struct printjob *pjob, int i; for (i = 0; i < ret; i++) { if (strcmp(q[i].fs_file, p) == 0) { + char *le_jobname = + log_escape(talloc_tos(), pjob->jobname); + pjob->sysjob = q[i].sysjob; DEBUG(5, ("new job %u (%s) matches sysjob %d\n", - pjob->jobid, jobname, pjob->sysjob)); + pjob->jobid, le_jobname, pjob->sysjob)); + + TALLOC_FREE(le_jobname); break; } } ret = 0; } if (pjob->sysjob == -1) { + char *le_jobname = log_escape(talloc_tos(), pjob->jobname); + DEBUG(2, ("failed to get sysjob for job %u (%s), tracking as " - "Unix job\n", pjob->jobid, jobname)); + "Unix job\n", pjob->jobid, le_jobname)); + + TALLOC_FREE(le_jobname); } -- 2.43.0 From 25d2e5bf0b7dd555a203cbf3a048f409f5fcf18a Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 May 2026 23:27:35 +0200 Subject: [PATCH 13/16] TODO CVE-2026-4480: s3:testparm: warn about 'print command' %J usage BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 Signed-off-by: Stefan Metzmacher --- source3/utils/testparm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source3/utils/testparm.c b/source3/utils/testparm.c index 14e6181f417f..9d69b7f917a4 100644 --- a/source3/utils/testparm.c +++ b/source3/utils/testparm.c @@ -953,6 +953,12 @@ static void do_per_share_checks(int s) "parameter is ignored when using CUPS libraries.\n\n", lp_servicename(talloc_tos(), lp_sub, s)); } + if (lp_printing(s) != PRINT_CUPS && + lp_printing(s) != PRINT_IPRINT && + *(lp_print_command(s)) != '\0') + { + // TODO warn about %J not in '%J'... + } vfs_objects = lp_vfs_objects(s); if (vfs_objects && str_list_check(vfs_objects, "fruit")) { -- 2.43.0 From 6058c895a58547f8d3c0ae50f8929bf863f665ac Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 23 Apr 2026 18:56:21 +0200 Subject: [PATCH 14/16] CVE-2026-4408: lib/util: introduce strstr_for_invalid_account_characters() This splits out the logic from samaccountname_bad_chars_check() in source4/dsdb/samdb/ldb_modules/samldb.c, this will be used in other places soon. BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Signed-off-by: Stefan Metzmacher --- lib/util/samba_util.h | 9 +++++++++ lib/util/util_str.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/lib/util/samba_util.h b/lib/util/samba_util.h index 2327ef5b9d2f..8dcf6e32254f 100644 --- a/lib/util/samba_util.h +++ b/lib/util/samba_util.h @@ -294,6 +294,15 @@ _PUBLIC_ size_t ascii_len_n(const char *src, size_t n); **/ _PUBLIC_ bool set_boolean(const char *boolean_string, bool *boolean); +/** + * Returns a pointer to the first invalid character in name. + * + * Passing a NULL pointer as name is not allowed! + * + * This returns NULL for a valid account name. + **/ +_PUBLIC_ const char *strstr_for_invalid_account_characters(const char *name); + /** * Convert a size specification like 16K into an integral number of bytes. **/ diff --git a/lib/util/util_str.c b/lib/util/util_str.c index 8fbfc32e0ecb..63c15c334561 100644 --- a/lib/util/util_str.c +++ b/lib/util/util_str.c @@ -218,3 +218,41 @@ _PUBLIC_ bool set_boolean(const char *boolean_string, bool *boolean) } return false; } + +_PUBLIC_ const char *strstr_for_invalid_account_characters(const char *name) +{ + /* + * Return a pointer to the first invalid character in the + * sAMAccountName, or NULL if the whole name is valid. + * + * The rules here are based on + * + * https://social.technet.microsoft.com/wiki/contents/articles/11216.active-directory-requirements-for-creating-objects.aspx + */ + size_t i; + + for (i = 0; name[i] != '\0'; i++) { + uint8_t c = name[i]; + const char *p = NULL; + + if (iscntrl(c)) { + return &name[i]; + } + + p = strchr("\"[]:;|=+*?<>/\\,", c); + if (p != NULL) { + return &name[i]; + } + } + + if (i == 0) { + return &name[i]; + } + + if (name[i - 1] == '.') { + i -= 1; + return &name[i]; + } + + return NULL; +} -- 2.43.0 From 7e38fd40483c7e40f880d8de02678701e4d78be1 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 18 Mar 2026 12:24:47 +0100 Subject: [PATCH 15/16] CVE-2026-4408: s3:samr-server: deny, mask, quote and/or escape username to 'check password script' We pass this on to the check password script, prevent remote command execution. BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Pair-Programmed-With: Douglas Bagnall Signed-off-by: Stefan Metzmacher --- source3/rpc_server/samr/srv_samr_chgpasswd.c | 108 +++++++++++++++++-- 1 file changed, 99 insertions(+), 9 deletions(-) diff --git a/source3/rpc_server/samr/srv_samr_chgpasswd.c b/source3/rpc_server/samr/srv_samr_chgpasswd.c index 41fe5bcc71ed..bc249e7263ce 100644 --- a/source3/rpc_server/samr/srv_samr_chgpasswd.c +++ b/source3/rpc_server/samr/srv_samr_chgpasswd.c @@ -54,6 +54,7 @@ #include "passdb.h" #include "auth.h" #include "lib/util/sys_rw.h" +#include "lib/util/util_str_escape.h" #include "librpc/rpc/dcerpc_samr.h" #include "lib/crypto/gnutls_helpers.h" @@ -1008,27 +1009,116 @@ static bool check_passwd_history(struct samu *sampass, const char *plaintext) /*********************************************************** ************************************************************/ +static NTSTATUS check_password_complexity_internal(TALLOC_CTX *tosctx, + const char *orig_cmd, + const char *username, + char **cmd_out) +{ + const char *inv = NULL; + char *cmd = NULL; + bool modified = false; + bool masked = false; + bool escaped = false; + + *cmd_out = NULL; + + if (username == NULL) { + return NT_STATUS_INVALID_USER_PRINCIPAL_NAME; + } + + /* + * This catches invalid characters in account names + * which might be problematic passing to a shell script. + */ + inv = strstr_for_invalid_account_characters(username); + if (inv != NULL) { + char *le_username = log_escape(tosctx, username); + + DBG_WARNING("username '%s' has invalid or dangerous characters\n", + le_username); + + TALLOC_FREE(le_username); + + return NT_STATUS_INVALID_USER_PRINCIPAL_NAME; + } + + /* + * This masks the remaining unsafe characters which + * are not already caught by strstr_for_invalid_account_characters() + * with '_'. + * + * Then it replaces %u with an single quoted + * and/or shell escaped version of the masked username. + */ + cmd = talloc_string_sub_unsafe(tosctx, + orig_cmd, + 'u', + username, + STRING_SUB_UNSAFE_CHARACTERS, + '_', + &modified, + &masked, + &escaped); + if (cmd == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* + * Now warn about unexpected values + */ + + if (masked) { + char *le_username = log_escape(tosctx, username); + + D_WARNING("CVE-2026-4408: " + "replaced %%u with masked value instead of: %s\n", + le_username); + D_WARNING("CVE-2026-4408: " + "You may use SAMBA_CPS_ACCOUNT_NAME inside " + "'check password script' instead of %%u"); + + TALLOC_FREE(le_username); + } + + if (escaped) { + D_WARNING("CVE-2026-4408: " + "strange quoting in 'check password script': %s\n", + orig_cmd); + D_WARNING("CVE-2026-4408: " + "You may use '%%u' for or SAMBA_CPS_ACCOUNT_NAME " + "inside of 'check password script'."); + } + + *cmd_out = cmd; + return NT_STATUS_OK; +} + + NTSTATUS check_password_complexity(const char *username, const char *fullname, const char *password, enum samPwdChangeReason *samr_reject_reason) { + int check_ret; + NTSTATUS status; TALLOC_CTX *tosctx = talloc_tos(); const struct loadparm_substitution *lp_sub = loadparm_s3_global_substitution(); - int check_ret; - char *cmd; + const char *orig_cmd = NULL; + char *cmd = NULL; - /* Use external script to check password complexity */ - if ((lp_check_password_script(tosctx, lp_sub) == NULL) - || (*(lp_check_password_script(tosctx, lp_sub)) == '\0')){ + orig_cmd = lp_check_password_script(tosctx, lp_sub); + if (orig_cmd == NULL || orig_cmd[0] == '\0') { return NT_STATUS_OK; } - cmd = talloc_string_sub(tosctx, lp_check_password_script(tosctx, lp_sub), "%u", - username); - if (!cmd) { - return NT_STATUS_PASSWORD_RESTRICTION; + /* note we don't use 'fullname' or 'password' here */ + status = check_password_complexity_internal(tosctx, + orig_cmd, + username, + &cmd); + if (!NT_STATUS_IS_OK(status)) { + return status; } check_ret = setenv("SAMBA_CPS_ACCOUNT_NAME", username, 1); -- 2.43.0 From d213ad841f74eb09343f2049858d0ac3b3a33c35 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 May 2026 23:27:35 +0200 Subject: [PATCH 16/16] TODO CVE-2026-4408: s3:testparm: warn about 'check password script' %u usage BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 Signed-off-by: Stefan Metzmacher --- source3/utils/testparm.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/source3/utils/testparm.c b/source3/utils/testparm.c index 9d69b7f917a4..99261eff82d3 100644 --- a/source3/utils/testparm.c +++ b/source3/utils/testparm.c @@ -856,6 +856,16 @@ static int do_global_checks(void) #endif } + if (lp_server_role() != ROLE_ACTIVE_DIRECTORY_DC && + !lp_rpc_start_on_demand_helpers() && + *(lp_check_password_script(talloc_tos(), lp_sub)) != '\0') + { + // only "rpc start on demand helpers = no" ??? + + // TODO check for %u without quoting '%u' + // suggest SAMBA_CPS_ACCOUNT_NAME??? + } + return ret; } -- 2.43.0