Backport of:

From e052dc44da9d12adfbebc51de4975fbadb2ce356 Mon Sep 17 00:00:00 2001
From: Michael R Sweet <msweet@msweet.org>
Date: Tue, 31 Mar 2026 15:55:50 -0400
Subject: [PATCH] Don't allow local certificates over the loopback interface,
 drop support for writing to plain files.

---
 CHANGES.md                     |  2 ++
 cups/auth.c                    | 30 ++++++----------------
 scheduler/auth.c               |  6 ++---
 scheduler/client.c             |  4 +--
 scheduler/ipp.c                |  6 ++---
 scheduler/job.c                | 46 ++++++++++++++++++----------------
 test/4.2-cups-printer-ops.test |  6 ++---
 test/5.1-lpadmin.sh            | 14 +++++------
 8 files changed, 52 insertions(+), 62 deletions(-)

#diff --git a/CHANGES.md b/CHANGES.md
#index 2e2973b70f..d8664c333b 100644
#--- a/CHANGES.md
#+++ b/CHANGES.md
#@@ -13,6 +13,8 @@ Changes in CUPS v2.4.17 (YYYY-MM-DD)
#   values.
# - CVE-2026-NNNNN: The scheduler did not always allocate enough memory for a
#   job's options string.
#+- CVE-2026-NNNNN: The scheduler incorrectly allowed local certificates over the
#+  loopback interface.
# - The scheduler followed symbolic links when cleaning out its temporary
#   directory (Issue #1448)
# - Updated `cupsFileGetConf` and `cupsFilePutConf` to escape more characters.
--- a/cups/auth.c
+++ b/cups/auth.c
@@ -92,7 +92,6 @@ static void	cups_gss_printf(OM_uint32 ma
 #    define	cups_gss_printf(major, minor, message)
 #  endif /* DEBUG */
 #endif /* HAVE_GSSAPI */
-static int	cups_is_local_connection(http_t *http);
 static int	cups_local_auth(http_t *http);
 
 
@@ -948,14 +947,6 @@ cups_gss_printf(OM_uint32  major_status,
 #  endif /* DEBUG */
 #endif /* HAVE_GSSAPI */
 
-static int				/* O - 0 if not a local connection */
-					/*     1  if local connection */
-cups_is_local_connection(http_t *http)	/* I - HTTP connection to server */
-{
-  if (!httpAddrLocalhost(http->hostaddr) && _cups_strcasecmp(http->hostname, "localhost") != 0)
-    return 0;
-  return 1;
-}
 
 /*
  * 'cups_local_auth()' - Get the local authorization certificate if
@@ -967,13 +958,7 @@ static int				/* O - 0 if available */
 					/*    -1 error */
 cups_local_auth(http_t *http)		/* I - HTTP connection to server */
 {
-#if defined(_WIN32) || defined(__EMX__)
- /*
-  * Currently _WIN32 and OS-2 do not support the CUPS server...
-  */
-
-  return (1);
-#else
+#if !_WIN32 && !__EMX__ && defined(AF_LOCAL)
   int			pid;		/* Current process ID */
   FILE			*fp;		/* Certificate file */
   char			trc[16],	/* Try Root Certificate parameter */
@@ -998,7 +983,7 @@ cups_local_auth(http_t *http)		/* I - HT
   * See if we are accessing localhost...
   */
 
-  if (!cups_is_local_connection(http))
+  if (httpAddrFamily(httpGetAddress(http)) != AF_LOCAL)
   {
     DEBUG_puts("8cups_local_auth: Not a local connection!");
     return (1);
@@ -1072,15 +1057,14 @@ cups_local_auth(http_t *http)		/* I - HT
   }
 #  endif /* HAVE_AUTHORIZATION_H */
 
-#  if defined(SO_PEERCRED) && defined(AF_LOCAL)
+#  ifdef SO_PEERCRED
  /*
   * See if we can authenticate using the peer credentials provided over a
   * domain socket; if so, specify "PeerCred username" as the authentication
   * information...
   */
 
-  if (http->hostaddr->addr.sa_family == AF_LOCAL &&
-      !getenv("GATEWAY_INTERFACE") &&	/* Not via CGI programs... */
+  if (!getenv("GATEWAY_INTERFACE") &&	/* Not via CGI programs... */
       cups_auth_find(www_auth, "PeerCred"))
   {
    /*
@@ -1104,7 +1088,7 @@ cups_local_auth(http_t *http)		/* I - HT
       return (0);
     }
   }
-#  endif /* SO_PEERCRED && AF_LOCAL */
+#  endif /* SO_PEERCRED */
 
   if ((schemedata = cups_auth_find(www_auth, "Local")) == NULL)
     return (1);
@@ -1164,7 +1148,7 @@ cups_local_auth(http_t *http)		/* I - HT
       return (0);
     }
   }
+#endif /* !_WIN32 && !__EMX__ && AF_LOCAL */
 
   return (1);
-#endif /* _WIN32 || __EMX__ */
 }
--- a/scheduler/auth.c
+++ b/scheduler/auth.c
@@ -318,7 +318,7 @@ cupsdAuthorize(cupsd_client_t *con)	/* I
   }
 #ifdef HAVE_AUTHORIZATION_H
   else if (!strncmp(authorization, "AuthRef ", 8) &&
-           httpAddrLocalhost(httpGetAddress(con->http)))
+           httpAddrFamily(httpGetAddress(con->http)) == AF_LOCAL)
   {
     OSStatus		status;		/* Status */
     char		authdata[HTTP_MAX_VALUE];
@@ -399,7 +399,7 @@ cupsdAuthorize(cupsd_client_t *con)	/* I
 #endif /* HAVE_AUTHORIZATION_H */
 #if defined(SO_PEERCRED) && defined(AF_LOCAL)
   else if (PeerCred != CUPSD_PEERCRED_OFF && !strncmp(authorization, "PeerCred ", 9) &&
-           con->http->hostaddr->addr.sa_family == AF_LOCAL && con->best)
+           httpAddrFamily(httpGetAddress(con->http)) == AF_LOCAL && con->best)
   {
    /*
     * Use peer credentials from domain socket connection...
@@ -489,7 +489,7 @@ cupsdAuthorize(cupsd_client_t *con)	/* I
   }
 #endif /* SO_PEERCRED && AF_LOCAL */
   else if (!strncmp(authorization, "Local", 5) &&
-	   httpAddrLocalhost(httpGetAddress(con->http)))
+	   httpAddrFamily(httpGetAddress(con->http)) == AF_LOCAL)
   {
    /*
     * Get Local certificate authentication data...
--- a/scheduler/client.c
+++ b/scheduler/client.c
@@ -2123,7 +2123,7 @@ cupsdSendHeader(
       strlcpy(auth_str, "Negotiate", sizeof(auth_str));
     }
 
-    if (con->best && !con->is_browser && !_cups_strcasecmp(httpGetHostname(con->http, NULL, 0), "localhost"))
+    if (con->best && !con->is_browser && httpAddrFamily(httpGetAddress(con->http)) == AF_LOCAL)
     {
      /*
       * Add a "trc" (try root certification) parameter for local
@@ -2143,7 +2143,7 @@ cupsdSendHeader(
       auth_size = sizeof(auth_str) - (size_t)(auth_key - auth_str);
 
 #if defined(SO_PEERCRED) && defined(AF_LOCAL)
-      if (PeerCred != CUPSD_PEERCRED_OFF && httpAddrFamily(httpGetAddress(con->http)) == AF_LOCAL)
+      if (PeerCred != CUPSD_PEERCRED_OFF)
       {
         strlcpy(auth_key, ", PeerCred", auth_size);
         auth_key += 10;
--- a/scheduler/ipp.c
+++ b/scheduler/ipp.c
@@ -5466,7 +5466,7 @@ create_local_printer(
   * Require local access to create a local printer...
   */
 
-  if (!httpAddrLocalhost(httpGetAddress(con->http)))
+  if (httpAddrFamily(httpGetAddress(con->http)) != AF_LOCAL)
   {
     send_ipp_status(con, IPP_STATUS_ERROR_FORBIDDEN, _("Only local users can create a local printer."));
     return;
@@ -5524,9 +5524,9 @@ create_local_printer(
     return;
   }
   ptr = ippGetString(device_uri, 0, NULL);
-  if (!ptr || !ptr[0])
+  if (!ptr || !ptr[0] || (strncmp(ptr, "ipp://", 6) && strncmp(ptr, "ipps://", 7)))
   {
-    send_ipp_status(con, IPP_STATUS_ERROR_BAD_REQUEST, _("Attribute \"%s\" has empty value."), "device-uri");
+    send_ipp_status(con, IPP_STATUS_ERROR_NOT_POSSIBLE, _("Bad device-uri \"%s\"."), ptr);
 
     return;
   }
--- a/scheduler/job.c
+++ b/scheduler/job.c
@@ -1157,35 +1157,39 @@ cupsdContinueJob(cupsd_job_t *job)	/* I
 	}
 	else
 	{
+	  char	scheme[32],		/* URI scheme */
+		userpass[32],		/* URI username:password */
+		host[256],		/* URI hostname */
+		resource[1024];		/* URI resource path (filename) */
+	  int	port;			/* URI port number */
+
+          httpSeparateURI(HTTP_URI_CODING_ALL, job->printer->device_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource));
+
 	  job->print_pipes[0] = -1;
-	  if (!strcmp(job->printer->device_uri, "file:/dev/null") ||
-	      !strcmp(job->printer->device_uri, "file:///dev/null"))
-	    job->print_pipes[1] = -1;
-	  else
+	  job->print_pipes[1] = -1;
+
+	  if (strcmp(resource, "/dev/null"))
 	  {
-	    if (!strncmp(job->printer->device_uri, "file:/dev/", 10))
-	      job->print_pipes[1] = open(job->printer->device_uri + 5,
-	                        	 O_WRONLY | O_EXCL);
-	    else if (!strncmp(job->printer->device_uri, "file:///dev/", 12))
-	      job->print_pipes[1] = open(job->printer->device_uri + 7,
-	                        	 O_WRONLY | O_EXCL);
-	    else if (!strncmp(job->printer->device_uri, "file:///", 8))
-	      job->print_pipes[1] = open(job->printer->device_uri + 7,
-	                        	 O_WRONLY | O_CREAT | O_TRUNC, 0600);
-	    else
-	      job->print_pipes[1] = open(job->printer->device_uri + 5,
-	                        	 O_WRONLY | O_CREAT | O_TRUNC, 0600);
+	    if (!FileDevice)
+	    {
+	      abort_message = "Stopping job because file: output is disabled.";
 
-	    if (job->print_pipes[1] < 0)
+              goto abort_job;
+	    }
+	    else if ((job->print_pipes[1] = open(resource, O_WRONLY | O_EXCL)) < 0)
 	    {
-	      abort_message = "Stopping job because the scheduler could not "
-	                      "open the output file.";
+	      abort_message = "Stopping job because the scheduler could not open the output file.";
 
               goto abort_job;
 	    }
+	    else
+	    {
+	     /*
+	      * Close this file on execute...
+	      */
 
-	    fcntl(job->print_pipes[1], F_SETFD,
-        	  fcntl(job->print_pipes[1], F_GETFD) | FD_CLOEXEC);
+	      fcntl(job->print_pipes[1], F_SETFD, fcntl(job->print_pipes[1], F_GETFD) | FD_CLOEXEC);
+	    }
           }
 	}
       }
--- a/test/4.2-cups-printer-ops.test
+++ b/test/4.2-cups-printer-ops.test
@@ -179,7 +179,7 @@
 	ATTR uri printer-uri $method://$hostname:$port/printers/Test2
 
 	GROUP printer
-	ATTR uri device-uri file:/tmp/Test2
+	ATTR uri device-uri file:///dev/null
 	ATTR enum printer-state 3
 	ATTR boolean printer-is-accepting-jobs true
 
@@ -205,7 +205,7 @@
 	ATTR uri printer-uri $method://$hostname:$port/printers/Test1
 
 	GROUP printer
-	ATTR uri device-uri file:/tmp/Test1
+	ATTR uri device-uri file:///dev/null
 	ATTR enum printer-state 3
 	ATTR boolean printer-is-accepting-jobs true
 	ATTR text printer-info "Test Printer 1"
--- a/test/5.1-lpadmin.sh
+++ b/test/5.1-lpadmin.sh
@@ -11,8 +11,8 @@
 
 echo "Add Printer Test"
 echo ""
-echo "    lpadmin -p Test3 -v file:/dev/null -E -m drv:///sample.drv/deskjet.ppd"
-$runcups $VALGRIND ../systemv/lpadmin -p Test3 -v file:/dev/null -E -m drv:///sample.drv/deskjet.ppd 2>&1
+echo "    lpadmin -p Test3 -v file:///dev/null -E -m drv:///sample.drv/deskjet.ppd"
+$runcups $VALGRIND ../systemv/lpadmin -p Test3 -v file:///dev/null -E -m drv:///sample.drv/deskjet.ppd 2>&1
 if test $? != 0; then
 	echo "    FAILED"
 	exit 1
@@ -28,8 +28,8 @@ echo ""
 
 echo "Modify Printer Test"
 echo ""
-echo "    lpadmin -p Test3 -v file:/tmp/Test3 -o PageSize=A4"
-$runcups $VALGRIND ../systemv/lpadmin -p Test3 -v file:/tmp/Test3 -o PageSize=A4 2>&1
+echo "    lpadmin -p Test3 -v file:///dev/null -o PageSize=A4"
+$runcups $VALGRIND ../systemv/lpadmin -p Test3 -v file:///dev/null -o PageSize=A4 2>&1
 if test $? != 0; then
 	echo "    FAILED"
 	exit 1
@@ -64,8 +64,8 @@ echo ""
 
 echo "Add a printer for cupSNMP/IPPSupplies test"
 echo ""
-echo "    lpadmin -p Test4 -E -v file:/dev/null -m drv:///sample.drv/zebra.ppd"
-$runcups $VALGRIND ../systemv/lpadmin -p Test4 -E -v file:/dev/null -m drv:///sample.drv/zebra.ppd 2>&1
+echo "    lpadmin -p Test4 -E -v file:///dev/null -m drv:///sample.drv/zebra.ppd"
+$runcups $VALGRIND ../systemv/lpadmin -p Test4 -E -v file:///dev/null -m drv:///sample.drv/zebra.ppd 2>&1
 if test $? != 0; then
 	echo "    FAILED"
 	exit 1
