OpenPrinting / cups

OpenPrinting CUPS Sources
https://openprinting.github.io/cups
Apache License 2.0
958 stars 174 forks source link

SEGV ppd-conflicts.c:210 in cupsResolveConflicts() #896

Closed Drawishe closed 3 months ago

Drawishe commented 4 months ago

Describe the bug

I have found SIGSEGV crashes with cups upstream version (c220e78), using custom harness for fuzzing, made of testppd.c and testcache.c unit-tests.

Here is minimized fuzzer-target to reproduce the bug

#include "ppd-private.h"
#include "file-private.h"
#include <stdio.h>
#include <unistd.h>

int               
main(void)
{
  int num_options = 0,
      finishings[1024];
  cups_option_t *options = NULL;

  char *options_str = (char *)malloc(sizeof(char) * 2048);
  if (fgets(options_str, 2048, stdin) == NULL)
    return 1;
  options_str[2047] = '\0';
  char **cups_options = (char **)malloc(sizeof(char *) * 2);
  char **cups_values = (char **)malloc(sizeof(char *) * 2);
  int elem_counter = 0, counter = 0;

  for (int i = 0; i < 2047; i++)
  {
    cups_options[elem_counter] = (char *)malloc(sizeof(char));
    cups_values[elem_counter] = (char *)malloc(sizeof(char));
    if (!options_str[i])
      break;
    counter = 0;
    while(options_str[i] != '=' && options_str[i] && options_str[i] != ' ')
    {
      cups_options[elem_counter] = (char *)realloc(cups_options[elem_counter], sizeof(char) * (counter + 2));
      cups_options[elem_counter][counter] = options_str[i];
      counter++;
      i++;
    }
    cups_options[elem_counter][counter] = '\0';
    if (options_str[i] == '=')
    {
      ++i;
      counter = 0;
      while(options_str[i] != ' ' && options_str[i])
      {
        cups_values[elem_counter] = (char *)realloc(cups_values[elem_counter], sizeof(char) * (counter + 2));
        cups_values[elem_counter][counter] = options_str[i];
        counter++;
        i++;
      }
      cups_values[elem_counter][counter] = '\0';
    }
    elem_counter++;
    cups_options = (char **)realloc(cups_options, sizeof(char *) * (elem_counter + 1));
    cups_values = (char **)realloc(cups_values, sizeof(char *) * (elem_counter + 1));
  }

  FILE *fp = fopen("testfile.ppd", "wb");
  if (!fp)
    return 1;
  char buffer[1024];
  while (fgets(buffer, sizeof(buffer), stdin) != NULL)
    fputs(buffer, fp);
  fclose(fp);

  ppd_file_t *ppd = NULL; 

  if ((ppd = ppdOpenFile("testfile.ppd")) == NULL)
    return 1;

  num_options = cupsParseOptions(options_str, num_options, &options);

  ppdMarkDefaults(ppd);
  cupsMarkOptions(ppd, num_options, options);
  for (int i = 0; i < elem_counter; i++)
  {
    cupsGetOption(cups_options[i], num_options, options);
    cupsGetConflicts(ppd, cups_options[i], cups_values[i], &options);
    cupsResolveConflicts(ppd, cups_options[i], cups_values[i], &num_options, &options);
  }
  return 0;
}

To Reproduce

Steps to reproduce the behaviour:

  1. build project with clang-17 compiler and ASAN
export CC=clang-17
export CXX=clang++-17
export CFLAGS="-fPIE -g -O1 -fsanitize=address"
export CXXFLAGS="-fPIE -g -O1 -fsanitize=address"
export LDFLAGS="-fPIE -fsanitize=address"
./configure --enable-static --disable-shared
make
  1. Move given fuzz-target and crash.input file into cups project

crash.tar.gz

  1. build fuzz target
clang-17 -fPIE -fsanitize=address -D_CUPS_SOURCE -D_FORTIFY_SOURCE=2 -D_REENTRANT -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_THREAD_SAFE -D_REENTRANT -I./ -I./cups/ -c FuzzTestppd.c
clang++-17 -fPIE -fsanitize=address -o FuzzTestppd FuzzTestppd.o -L./cups/ -lcups -lcupsimage -lssl -lcrypto -lz -lpthread -l:libavahi-client.so -l:libavahi-common.so -l:libdbus-1.so -lsystemd 
  1. run FuzzTestppd with given input
./FuzzTestppd < crash.input 
AddressSanitizer:DEADLYSIGNAL
=================================================================
==3407934==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x55c516efc46a bp 0x7fff595743f0 sp 0x7fff59574280 T0)
==3407934==The signal is caused by a READ memory access.
==3407934==Hint: address points to the zero page.
    #0 0x55c516efc46a in cupsResolveConflicts /home/as/cups-oss/cups-opensource/cups/ppd-conflicts.c:210
    #1 0x55c516dec16f in main /home/as/cups-oss/cups-opensource/FuzzTestppd.c:75:5
    #2 0x7fe8ce9671c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #3 0x7fe8ce967284 in __libc_start_main csu/../csu/libc-start.c:360:3
    #4 0x55c516d103b0 in _start (/home/as/cups-oss/cups-opensource/FuzzTestppd+0xff3b0) (BuildId: d2bb824ef64e5ffa)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/as/cups-oss/cups-opensource/cups/ppd-conflicts.c:210 in cupsResolveConflicts
==3407934==ABORTING

This problem arises due to the lack of verification of size structure for NULL value in cupsResolveConflicts. The existence of the options structure is checked, however, the elements of this structure may not exist. In that case, we have num_options which is not equal 0, and non-existent values of the options structure, which is causes SEGV, when we try to call options structure elements in ppd-conflicts.c:210.

https://github.com/OpenPrinting/cups/blob/c220e7836b700563516e152ce629ff59b1b23de7/cups/ppd-conflicts.c#L199 https://github.com/OpenPrinting/cups/blob/c220e7836b700563516e152ce629ff59b1b23de7/cups/ppd-conflicts.c#L210-L211

Solution:

The solution is to add check for NULL value and return 0 if it's true

209:  for (i = 0; i < *num_options; i ++)
210:  {
211:    if (!*options)
212:      return (0);
213:    num_newopts = cupsAddOption((*options)[i].name, (*options)[i].value,
214:                                num_newopts, &newopts);
215:  }

System information:

michaelrsweet commented 3 months ago

OK, unless you can reproduce this with valid API usage, this isn't something that can be fixed.