Talos Vulnerability Report

TALOS-2018-0529

NASA CFITSIO Multiple Stack Overflow Code Execution Vulnerabilities

April 12, 2018
CVE Number

CVE-2018-3846

Summary

Multiple exploitable buffer overflow vulnerabilities exist in image parsing functionality of the CFITSIO library version 3.42. Specially crafted images parsed via the library, can cause a stack-based buffer overflow overwriting arbitrary data. An attacker can deliver an FIT image to trigger this vulnerability and potentially gain code execution.

Tested Versions

NASA CFITSIO 3.42

Product URLs

https://heasarc.gsfc.nasa.gov/fitsio/fitsio.html

CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-121: Stack-based Buffer Overflow

Details

CFITSIO is a library written in C, for reading and writing data files in FITS data format. This format is the standard astronomical data format endorsed by both NASA and the IAU. FITS is primarily designed to store scientific data sets consisting of multi-dimensional arrays and 2-dimensional tables containing rows and columns of data. This software is used extensively and is the primary library used when writing an application to handle the FITS format. Among other things CFITSIO is widely used in ground-based observatories as well as orbiting astronomical observatories in their ground data processing pipeline systems. For more users see, https://heasarc.gsfc.nasa.gov/docs/software/fitsio/major_users.html.

The vulnerabilities arise in the ffgphd and ffgtkn functions. The ffgphd function is responsible for getting the primary header data from a FITS image. This functions main purpose is to parse the header keywords and validate them to ensure they conform to the FITS standard. The function also returns the necessary data to parse the rest of the image. Below is a look at the basic template for parsing seen throughout.

    #define FLEN_KEYWORD   75 [0]
    #define FLEN_ERRMSG    81 [1]

    
    [2]

    char message[FLEN_ERRMSG], keyword[FLEN_KEYWORD];
    char card[FLEN_CARD];
    char name[FLEN_KEYWORD], value[FLEN_VALUE], comm[FLEN_COMMENT];
    char xtension[FLEN_VALUE]

    /*----------------------------------------------------------------*/
    /*  Get 2nd keyword;  test whether it is BITPIX with legal value  */
    /*----------------------------------------------------------------*/
    fits_read_keyn (fptr, 2, name, value, comm, status);  /* BITPIX = 2nd keyword */  [3]

    if (strcmp(name, "BITPIX"))  [4]
    {
        sprintf(message,
        "Second keyword of the extension is not BITPIX: %s", name);  [5]
        fits_write_errmsg(message);
        return(*status = NO_BITPIX);
    }

At location 0 and 1, the defines used throughout the function are given to help better understand the sizes being used. Likewise at 2, the local variables that will be used for the function are defined.

At 3, we see the function read in the keyword directly from the header. This value is then compared with the expected value at 4 and if it does not match we fall into the if statement. There is then a vulnerable call to the function sprintf at 5. Looking at the sprintf manual page it states,

 The sprintf() functions are easily misused in a manner which enables malicious users to arbitrarily change
 a running program's functionality through a buffer overflow attack.  Because sprintf() assume an infinite-
 ly long string, callers must be careful not to overflow the actual space; this is often hard to assure.  For safety, programmers should use the snprintf() interface instead.

Due to the lack of bounds checking as stated above this call will cause a buffer overflow. The name parameter is taken directly from the user and although restricted in size to 75 bytes, 0, the added error message makes it longer than the 81 bytes of available space in the buffer, 1. This causes an exploitable stack based buffer overflow. Below is a list of the locations inside of the ffgphd function in which this vulnerability arises.

2778: unknown = 1; /* unknown type of extension; press on anyway */ sprintf(message, “This is not an IMAGE extension: %s”, value); ffpmsg(message);

2842: if (strcmp(name, “BITPIX”)) { sprintf(message, “Second keyword of the extension is not BITPIX: %s”, name); fits_write_errmsg(message); return(*status = NO_BITPIX); }

2850: if (ffc2ii(value, &longbitpix, status) > 0) { sprintf(message, “Value of BITPIX keyword is not an integer: %s”, value); ffpmsg(message); return(*status = BAD_BITPIX); }

2950: if (fftrec(name, status) > 0) /* test keyword name; catches no END */ { sprintf(message, “Name of keyword no. %d contains illegal character(s): %s”, nextkey, name); ffpmsg(message);

2961: if (!strcmp(name, “BSCALE”) && bscale) { nspace = 0; / reset count of blank keywords / ffpsvc(card, value, comm, status); / parse value and comment */

        if (ffc2dd(value, bscale, status) > 0) /* convert to double */
        {
            /* reset error status and continue, but still issue warning */
            *status = tstatus;
            *bscale = 1.0;

            sprintf(message,
            "Error reading BSCALE keyword value as a double: %s", value);
            ffpmsg(message);
        }

2978: else if (!strcmp(name, “BZERO”) && bzero) { nspace = 0; / reset count of blank keywords / ffpsvc(card, value, comm, status); / parse value and comment */

        if (ffc2dd(value, bzero, status) > 0) /* convert to double */
        {
            /* reset error status and continue, but still issue warning */
            *status = tstatus;
            *bzero = 0.0;

            sprintf(message,
            "Error reading BZERO keyword value as a double: %s", value);
            ffpmsg(message);
        }
    }

2995: else if (!strcmp(name, “BLANK”) && blank) { nspace = 0; / reset count of blank keywords / ffpsvc(card, value, comm, status); / parse value and comment */

        if (ffc2jj(value, blank, status) > 0) /* convert to LONGLONG */
        {
            /* reset error status and continue, but still issue warning */
            *status = tstatus;
            *blank = NULL_UNDEFINED;

            sprintf(message,
            "Error reading BLANK keyword value as an integer: %s", value);
            ffpmsg(message);
        }
    }

3012: else if (!strcmp(name, “PCOUNT”) && pcount) { nspace = 0; / reset count of blank keywords / ffpsvc(card, value, comm, status); / parse value and comment */

        if (ffc2ii(value, pcount, status) > 0) /* convert to long */
        {
            sprintf(message,
            "Error reading PCOUNT keyword value as an integer: %s", value);
            ffpmsg(message);
        }
    }

3025: else if (!strcmp(name, “GCOUNT”) && gcount) { nspace = 0; / reset count of blank keywords / ffpsvc(card, value, comm, status); / parse value and comment */

        if (ffc2ii(value, gcount, status) > 0) /* convert to long */
        {
            sprintf(message,
            "Error reading GCOUNT keyword value as an integer: %s", value);
            ffpmsg(message);
        }
    }

The vulnerability again arises in the ffgtkn function. The relevant code is shown below.

int ffgtkn(fitsfile *fptr,  /* I - FITS file pointer              */
           int numkey,      /* I - number of the keyword to read  */
           char *name,      /* I - expected name of the keyword   */
           long *value,     /* O - integer value of the keyword   */
           int *status)     /* IO - error status                  */
{

        char keyname[FLEN_KEYWORD], valuestring[FLEN_VALUE];              
        char comm[FLEN_COMMENT], message[FLEN_ERRMSG];
  

        if (fits_get_keyname(fptr, numkey, keyname, valuestring, comm, status) <= 0)          [6]
        {
              if (strcmp(keyname, name) )
                    *status = BAD_ORDER;  

              else{
                ffc2ii(valuestring, value, status);  /* convert to integer */        [7]

                if (*status > 0 || *value < 0 )
                   *status = NOT_POS_INT;
              }
            

            if (*status > 0)
            {
                sprintf(message,                                                    [8]
                  "ffgtkn found unexpected keyword or value for keyword no. %d.",
                  numkey);
                ffpmsg(message);

                sprintf(message,
                  " Expected positive integer keyword %s, but instead", name);
                ffpmsg(message);

                sprintf(message,
                  " found keyword %s with value %s", keyname, valuestring);
                ffpmsg(message);
            }
        }

As before the name is taken directly from the image supplied at 6. If the value fails to be converted to an integer at 7, the error status is set. The function subsequently goes into the if statement at 8 and the vulnerable sprintf function is called.

Timeline

2018-02-23 - Vendor Disclosure
2018-03-21 - Vendor Patched
2018-04-12 - Public Release

Credit

Discovered by Tyler Bohan of Cisco Talos.