The Samba-Bugzilla – Attachment 18962 Details for
Bug 16033
CVE-2026-4480 [SECURITY] Unauthenticated Remote Code Execution
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
Work in progress (fails autobuild) for CVE-2026-4480/CVE-2026-4408
CVE-2026-4480-CVE-2026-4408-metze-wip-01.txt (text/plain), 50.32 KB, created by
Stefan Metzmacher
on 2026-05-08 22:42:10 UTC
(
hide
)
Description:
Work in progress (fails autobuild) for CVE-2026-4480/CVE-2026-4408
Filename:
MIME Type:
Creator:
Stefan Metzmacher
Created:
2026-05-08 22:42:10 UTC
Size:
50.32 KB
patch
obsolete
>From daef585f196e2fec1f541d553193383a02e7655e Mon Sep 17 00:00:00 2001 >From: Stefan Metzmacher <metze@samba.org> >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 <metze@samba.org> >Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz> >--- > 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<li;i++) { > switch (insert[i]) { > case '$': >- /* allow a trailing $ >- * (as in machine accounts) */ >- if (allow_trailing_dollar && (i == li - 1 )) { >- p[i] = insert[i]; >- break; >- } >- FALL_THROUGH; > case '`': > case '"': > case '\'': >@@ -107,17 +99,9 @@ static void string_sub2(char *s,const char *pattern, const char *insert, size_t > } > s = p + li; > ls = ls + li - lp; >- >- if (replace_once) >- break; > } > } > >-void string_sub(char *s,const char *pattern, const char *insert, size_t len) >-{ >- string_sub2( s, pattern, insert, len, true, false, false ); >-} >- > /** > Similar to string_sub() but allows for any character to be substituted. > Use with caution! >-- >2.43.0 > > >From 93188804636d949e9df455bf419fd31e1d72b8bb Mon Sep 17 00:00:00 2001 >From: Stefan Metzmacher <metze@samba.org> >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 <metze@samba.org> >--- > 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<li;i++) { >- switch (in[i]) { >+ 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); >+ 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<li; i++) { >+ switch (insert[i]) { > case '$': >- /* allow a trailing $ >- * (as in machine accounts) */ >+ /* >+ * allow a trailing $ (as in machine accounts) >+ */ > if (allow_trailing_dollar && (i == li - 1 )) { > break; > } >@@ -204,34 +214,18 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, > case '\r': > case '\n': > if (remove_unsafe_characters) { >- in[i] = '_'; >- break; >+ p[i] = '_'; >+ continue; > } > > FALL_THROUGH; > default: > /* ok */ > break; >- } >- } >- >- 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); >- 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 <metze@samba.org> >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 <metze@samba.org> >--- > 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<li;i++) { >- switch (insert[i]) { >- case '$': >- case '`': >- case '"': >- case '\'': >- case ';': >- case '%': >- case '\r': >- case '\n': >- if ( remove_unsafe_characters ) { >- p[i] = '_'; >- /* yes this break should be here >- * since we want to fall throw if >- * not replacing unsafe chars */ >- break; >- } >- FALL_THROUGH; >- default: >- p[i] = insert[i]; >- } >+ /* >+ * Without allow_trailing_dollar we don't >+ * need to calculate is_last... >+ */ >+ const bool is_last = false; >+ const bool allow_trailing_dollar = false; >+ >+ p[i] = mask_unsafe_character(insert[i], >+ is_last, >+ allow_trailing_dollar, >+ unsafe_characters, >+ safe_character); > } > s = p + li; > ls = ls + li - lp; >@@ -157,9 +178,11 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, > bool replace_once, > bool allow_trailing_dollar) > { >- char *p; >- char *s; >- char *string; >+ const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS; >+ const char safe_character = '_'; >+ char *p = NULL, >+ char *s = NULL; >+ char *string = NULL; > ssize_t ls,lp,li,ld, i; > > if (!insert || !pattern || !*pattern || !src) { >@@ -195,36 +218,14 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, > if (li != lp) { > memmove(p+li,p+lp,strlen(p+lp)+1); > } >- for (i=0; i<li; i++) { >- switch (insert[i]) { >- case '$': >- /* >- * allow a trailing $ (as in machine accounts) >- */ >- if (allow_trailing_dollar && (i == li - 1 )) { >- break; >- } >- >- FALL_THROUGH; >- case '`': >- case '"': >- case '\'': >- case ';': >- case '%': >- case '\r': >- case '\n': >- if (remove_unsafe_characters) { >- p[i] = '_'; >- continue; >- } >- >- FALL_THROUGH; >- default: >- /* ok */ >- break; >- } >+ for (i=0; i < li; i++) { >+ bool is_last = (i == li - 1); > >- p[i] = insert[i]; >+ p[i] = mask_unsafe_character(insert[i], >+ is_last, >+ allow_trailing_dollar, >+ unsafe_characters, >+ safe_character); > } > s = p + li; > ls += ld; >diff --git a/lib/util/substitute.h b/lib/util/substitute.h >index 3134cfcdea54..e1a82859daca 100644 >--- a/lib/util/substitute.h >+++ b/lib/util/substitute.h >@@ -26,6 +26,8 @@ > > #include <talloc.h> > >+#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 <metze@samba.org> >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 <metze@samba.org> >--- > 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 <metze@samba.org> >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 <metze@samba.org> >--- > 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 <metze@samba.org> >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 <metze@samba.org> >--- > 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<li;i++) { >- switch (in[i]) { >- case '$': >- /* allow a trailing $ >- * (as in machine accounts) */ >- if (allow_trailing_dollar && (i == li - 1 )) { >- break; >- } >- FALL_THROUGH; >- case '`': >- case '"': >- case '\'': >- case ';': >- case '%': >- case '\r': >- case '\n': >- if ( remove_unsafe_characters ) { >- in[i] = '_'; >- break; >- } >- FALL_THROUGH; >- default: >- /* ok */ >- break; >- } >- } > >- while ((p = strstr_m(s,pattern))) { >- if (ld > 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 <metze@samba.org> >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 <metze@samba.org> >--- > 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 <talloc.h> > >-#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 <metze@samba.org> >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 <metze@samba.org> >--- > 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 <talloc.h> > >-#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 <metze@samba.org> >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 <metze@samba.org> >--- > 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 <douglas.bagnall@catalyst.net.nz> >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 <metze@samba.org> >--- > 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 <stdbool.h> > #include <talloc.h> > > 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 <metze@samba.org> >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 <douglas.bagnall@catalyst.net.nz> > >Signed-off-by: Stefan Metzmacher <metze@samba.org> >--- > 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 '<masked_value>' 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 <metze@samba.org> >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 <metze@samba.org> >--- > 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 <metze@samba.org> >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 <metze@samba.org> >--- > 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 <metze@samba.org> >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 <metze@samba.org> >--- > 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 <metze@samba.org> >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 <douglas.bagnall@catalyst.net.nz> > >Signed-off-by: Stefan Metzmacher <metze@samba.org> >--- > 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 <metze@samba.org> >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 <metze@samba.org> >--- > 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 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Raw
Actions:
View
Attachments on
bug 16033
:
18903
|
18905
|
18915
|
18916
|
18917
|
18918
|
18919
|
18925
|
18962
|
18968
|
18969
|
18972
|
18973
|
18974
|
18975