Skip to content

Commit 8f27403

Browse files
Merge pull request from GHSA-gpxc-v2m8-fr3x
* beh backend: Use execv() instead of system() - CVE-2023-24805 With execv() command line arguments are passed as separate strings and not the full command line in a single string. This prevents arbitrary command execution by escaping the quoting of the arguments in a job with forged job title. * beh backend: Extra checks against odd/forged input - CVE-2023-24805 - Do not allow '/' in the scheme of the URI (= backend executable name), to assure that only backends inside /usr/lib/cups/backend/ are used. - Pre-define scheme buffer to empty string, to be defined for case of uri being NULL. - URI must have ':', to split off scheme, otherwise error. - Check return value of snprintf() to create call path for backend, to error out on truncation of a too long scheme or on complete failure due to a completely odd scheme. * beh backend: Further improvements - CVE-2023-24805 - Use strncat() instead of strncpy() for getting scheme from URI, the latter does not require setting terminating zero byte in case of truncation. - Also exclude "." or ".." as scheme, as directories are not valid CUPS backends. - Do not use fprintf() in sigterm_handler(), to not interfere with a fprintf() which could be running in the main process when sigterm_handler() is triggered. - Use "static volatile int" for global variable job_canceled.
1 parent a4809b8 commit 8f27403

File tree

1 file changed

+84
-25
lines changed

1 file changed

+84
-25
lines changed

backend/beh.c

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@
2626
#include <signal.h>
2727
#include <string.h>
2828
#include <errno.h>
29+
#include <sys/wait.h>
2930

3031

3132
//
3233
// Local globals...
3334
//
3435

35-
static int job_canceled = 0; // Set to 1 on SIGTERM
36+
static volatile int job_canceled = 0; // Set to 1 on SIGTERM
3637

3738

3839
//
@@ -237,21 +238,44 @@ call_backend(char *uri, // I - URI of final destination
237238
char *filename) // I - File name of input data
238239
{
239240
const char *cups_serverbin; // Location of programs
241+
char *backend_argv[8]; // Arguments for called CUPS backend
240242
char scheme[1024], // Scheme from URI
241243
*ptr, // Pointer into scheme
242-
cmdline[65536]; // Backend command line
243-
int retval;
244+
backend_path[2048]; // Backend path
245+
int pid,
246+
wait_pid,
247+
wait_status,
248+
retval = 0;
249+
int bytes;
250+
244251

245252
//
246253
// Build the backend command line...
247254
//
248255

249-
strncpy(scheme, uri, sizeof(scheme) - 1);
250-
if (strlen(uri) > 1023)
251-
scheme[1023] = '\0';
256+
scheme[0] = '\0';
257+
strncat(scheme, uri, sizeof(scheme) - 1);
252258
if ((ptr = strchr(scheme, ':')) != NULL)
253259
*ptr = '\0';
254-
260+
else
261+
{
262+
fprintf(stderr,
263+
"ERROR: beh: Invalid URI, no colon (':') to mark end of scheme part.\n");
264+
exit (CUPS_BACKEND_FAILED);
265+
}
266+
if (strchr(scheme, '/'))
267+
{
268+
fprintf(stderr,
269+
"ERROR: beh: Invalid URI, scheme contains a slash ('/').\n");
270+
exit (CUPS_BACKEND_FAILED);
271+
}
272+
if (!strcmp(scheme, ".") || !strcmp(scheme, ".."))
273+
{
274+
fprintf(stderr,
275+
"ERROR: beh: Invalid URI, scheme (\"%s\") is a directory.\n",
276+
scheme);
277+
exit (CUPS_BACKEND_FAILED);
278+
}
255279
if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
256280
cups_serverbin = CUPS_SERVERBIN;
257281

@@ -261,16 +285,25 @@ call_backend(char *uri, // I - URI of final destination
261285
"ERROR: beh: Direct output into a file not supported.\n");
262286
exit (CUPS_BACKEND_FAILED);
263287
}
264-
else
265-
snprintf(cmdline, sizeof(cmdline),
266-
"%s/backend/%s '%s' '%s' '%s' '%s' '%s' %s",
267-
cups_serverbin, scheme, argv[1], argv[2], argv[3],
268-
// Apply number of copies only if beh was called with a
269-
// file name and not with the print data in stdin, as
270-
// backends should handle copies only if they are called
271-
// with a file name
272-
(argc == 6 ? "1" : argv[4]),
273-
argv[5], filename);
288+
289+
backend_argv[0] = uri;
290+
backend_argv[1] = argv[1];
291+
backend_argv[2] = argv[2];
292+
backend_argv[3] = argv[3];
293+
backend_argv[4] = (argc == 6 ? "1" : argv[4]);
294+
backend_argv[5] = argv[5];
295+
backend_argv[6] = filename;
296+
backend_argv[7] = NULL;
297+
298+
bytes = snprintf(backend_path, sizeof(backend_path),
299+
"%s/backend/%s", cups_serverbin, scheme);
300+
if (bytes < 0 || bytes >= sizeof(backend_path))
301+
{
302+
fprintf(stderr,
303+
"ERROR: beh: Invalid scheme (\"%s\"), could not determing backend path.\n",
304+
scheme);
305+
exit (CUPS_BACKEND_FAILED);
306+
}
274307

275308
//
276309
// Overwrite the device URI and run the actual backend...
@@ -279,17 +312,41 @@ call_backend(char *uri, // I - URI of final destination
279312
setenv("DEVICE_URI", uri, 1);
280313

281314
fprintf(stderr,
282-
"DEBUG: beh: Executing backend command line \"%s\"...\n",
283-
cmdline);
315+
"DEBUG: beh: Executing backend command line \"%s '%s' '%s' '%s' '%s' '%s'%s%s\"...\n",
316+
backend_path, backend_argv[1], backend_argv[2], backend_argv[3],
317+
backend_argv[4], backend_argv[5],
318+
(backend_argv[6] && backend_argv[6][0] ? " " : ""),
319+
(backend_argv[6] && backend_argv[6][0] ? backend_argv[6] : ""));
284320
fprintf(stderr,
285321
"DEBUG: beh: Using device URI: %s\n",
286322
uri);
287323

288-
retval = system(cmdline) >> 8;
324+
if ((pid = fork()) == 0)
325+
{
326+
retval = execv(backend_path, backend_argv);
289327

290-
if (retval == -1)
291-
fprintf(stderr, "ERROR: Unable to execute backend command line: %s\n",
292-
strerror(errno));
328+
if (retval == -1)
329+
fprintf(stderr, "ERROR: Unable to execute backend: %s\n",
330+
strerror(errno));
331+
exit (CUPS_BACKEND_FAILED);
332+
}
333+
else if (pid < 0)
334+
{
335+
fprintf(stderr, "ERROR: Unable to fork for backend\n");
336+
return (CUPS_BACKEND_FAILED);
337+
}
338+
339+
while ((wait_pid = wait(&wait_status)) < 0 && errno == EINTR);
340+
341+
if (wait_pid >= 0 && wait_status)
342+
{
343+
if (WIFEXITED(wait_status))
344+
retval = WEXITSTATUS(wait_status);
345+
else if (WTERMSIG(wait_status) != SIGTERM)
346+
retval = WTERMSIG(wait_status);
347+
else
348+
retval = 0;
349+
}
293350

294351
return (retval);
295352
}
@@ -304,8 +361,10 @@ sigterm_handler(int sig) // I - Signal number (unused)
304361
{
305362
(void)sig;
306363

307-
fprintf(stderr,
308-
"DEBUG: beh: Job canceled.\n");
364+
const char * const msg = "DEBUG: beh: Job canceled.\n";
365+
// The if() is to eliminate the return value and silence the warning
366+
// about an unused return value.
367+
if (write(2, msg, strlen(msg)));
309368

310369
if (job_canceled)
311370
_exit(CUPS_BACKEND_OK);

0 commit comments

Comments
 (0)