]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
CVE-2026-4480: s3:printing: mask and/or single quote jobname passed as %J to "print...
authorStefan Metzmacher <metze@samba.org>
Sun, 15 Mar 2026 18:15:14 +0000 (19:15 +0100)
committerStefan Metzmacher <metze@samba.org>
Tue, 26 May 2026 12:51:32 +0000 (12:51 +0000)
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.

Using single quotes (directly) around %J, '%J' would avoid the
problem, we now try to autodetect if we can use '%J' implicitly
or we fallback to a fixed "__CVE-2026-4480_FallbackJobname__"
string instead of the client provided jobname.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
source3/printing/print_generic.c

index 855de4ca1f3624f106a77fe2fa704727320f3253..a14d1d441df35fca270ffdc8a448c2870ee6203b 100644 (file)
@@ -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,52 @@ 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,
+                                      const char *fallback_jobname)
+{
+       char *cmd = NULL;
+       bool modified = false;
+       bool masked = false;
+       bool mixed_fallback = false;
+
+       /*
+        * This replaces unsafe characters with '_'.
+        * We also mask forward and backslash here.
+        *
+        * Then it replaces %J with an single quoted
+        * version of the masked jobname or it falls
+        * back to fallback_jobname is the print command
+        * uses strange mixed quoting.
+        */
+
+#define JOBNAME_UNSAFE_CHARACTERS \
+       STRING_SUB_UNSAFE_CHARACTERS "/\\"
+
+       cmd = talloc_string_sub_unsafe(mem_ctx,
+                                      orig_cmd,
+                                      'J',
+                                      unsafe_jobname,
+                                      JOBNAME_UNSAFE_CHARACTERS,
+                                      '_',
+                                      fallback_jobname,
+                                      &modified,
+                                      &masked,
+                                      &mixed_fallback);
+       if (cmd == NULL) {
+               return NULL;
+       }
+
+       /*
+        * The caller already checked talloc_string_sub_mixed_quoting()
+        * and warned the admin, so we don't check mixed_fallback
+        * here
+        */
+
+       return cmd;
+}
+
 /****************************************************************************
  Submit a file for printing - called from print_job_end()
 ****************************************************************************/
@@ -222,11 +269,12 @@ 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;
        print_status_struct status;
+       const char *jobname = "No Document Name";
 
        /* we print from the directory path to give the best chance of
            parsing the lpq output */
@@ -255,24 +303,48 @@ static int generic_job_submit(int snum, struct printjob *pjob,
                return -1;
        }
 
-       jobname = talloc_strdup(ctx, pjob->jobname);
-       if (!jobname) {
-               ret = -1;
-               goto out;
+       if (pjob->jobname[0] != '\0') {
+               jobname = pjob->jobname;
        }
-       jobname = talloc_string_sub(ctx, jobname, "'", "_");
-       if (!jobname) {
-               ret = -1;
-               goto out;
+
+       print_cmd = lp_print_command(snum);
+       if (print_cmd != NULL) {
+               const char *invalid_jobname = "__CVE-2026-4480_FallbackJobname__";
+
+               if (talloc_string_sub_mixed_quoting(print_cmd, 'J')) {
+                       /*
+                        * The admin used a strange mixture of
+                        * single and double quotes, fallback
+                        * to InvalidDocumentName and warn about
+                        * it, so that the admin can adjust to
+                        * the use single quotes directly around %J,
+                        * e.g. '%J'.
+                        */
+                       jobname = invalid_jobname;
+                       D_WARNING("CVE-2026-4480: printer %s "
+                                 "strange quoting in 'print command', "
+                                 "falling back to jobname=%s, "
+                                 "use testparm to fix the configuration\n",
+                                 lp_printername(talloc_tos(), lp_sub, snum),
+                                 invalid_jobname);
+               }
+
+               print_cmd = replace_print_cmd_J(ctx,
+                                               print_cmd,
+                                               jobname,
+                                               invalid_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 +365,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(), 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(), 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);
        }