Commit message: input: check end of input before reading a byte

When reading bytes we weren't check that the index wasn't
out of bound and this could produce an invalid read which
could deal to a security bug.
CVE: CVE-2017-7960
CWE: CWE-125
Vulnerability description: The cr_input_new_from_uri function in cr-input.c in libcroco 0.6.11 and 0.6.12 allows remote attackers to cause a denial of service (heap-based buffer over-read) via a crafted CSS file.

Project: libcroco

cr_input_set_cur_index (CRInput * a_this, glong a_index)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        PRIVATE (a_this)->next_byte_index = a_index;

        return CR_OK;
}

Project: libcroco

cr_input_get_cur_pos (CRInput const * a_this, CRInputPos * a_pos)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this) && a_pos,
                              CR_BAD_PARAM_ERROR);

        a_pos->next_byte_index = PRIVATE (a_this)->next_byte_index;
        a_pos->line = PRIVATE (a_this)->line;
        a_pos->col = PRIVATE (a_this)->col;
        a_pos->end_of_line = PRIVATE (a_this)->end_of_line;
        a_pos->end_of_file = PRIVATE (a_this)->end_of_input;

        return CR_OK;
}

Project: libcroco

cr_input_new_from_buf (guchar * a_buf,
                       gulong a_len,
                       enum CREncoding a_enc,
                       gboolean a_free_buf)
{
        CRInput *result = NULL;
        enum CRStatus status = CR_OK;
        CREncHandler *enc_handler = NULL;
        gulong len = a_len;

        g_return_val_if_fail (a_buf, NULL);

        result = cr_input_new_real ();
        g_return_val_if_fail (result, NULL);

        /*transform the encoding in utf8 */
        if (a_enc != CR_UTF_8) {
                enc_handler = cr_enc_handler_get_instance (a_enc);
                if (!enc_handler) {
                        goto error;
                }

                status = cr_enc_handler_convert_input
                        (enc_handler, a_buf, &len,
                         &PRIVATE (result)->in_buf,
                         &PRIVATE (result)->in_buf_size);
                if (status != CR_OK)
                        goto error;
                PRIVATE (result)->free_in_buf = TRUE;
                if (a_free_buf == TRUE && a_buf) {
                        g_free (a_buf) ;
                        a_buf = NULL ;
                }                
                PRIVATE (result)->nb_bytes = PRIVATE (result)->in_buf_size;
        } else {
                PRIVATE (result)->in_buf = (guchar *) a_buf;
                PRIVATE (result)->in_buf_size = a_len;
                PRIVATE (result)->nb_bytes = a_len;
                PRIVATE (result)->free_in_buf = a_free_buf;
        }
        PRIVATE (result)->line = 1;
        PRIVATE (result)->col =  0;
        return result;

 error:
        if (result) {
                cr_input_destroy (result);
                result = NULL;
        }

        return NULL;
}

Project: libcroco

cr_input_peek_byte2 (CRInput const * a_this, gulong a_offset, gboolean * a_eof)
{
        guchar result = 0;
        enum CRStatus status = CR_ERROR;

        g_return_val_if_fail (a_this && PRIVATE (a_this), 0);

        if (a_eof)
                *a_eof = FALSE;

        status = cr_input_peek_byte (a_this, CR_SEEK_CUR, a_offset, &result);

        if ((status == CR_END_OF_INPUT_ERROR)
            && a_eof)
                *a_eof = TRUE;

        return result;
}

Project: libcroco

cr_input_seek_index (CRInput * a_this, enum CRSeekPos a_origin, gint a_pos)
{

        glong abs_offset = 0;

        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        switch (a_origin) {

        case CR_SEEK_CUR:
                abs_offset = PRIVATE (a_this)->next_byte_index - 1 + a_pos;
                break;

        case CR_SEEK_BEGIN:
                abs_offset = a_pos;
                break;

        case CR_SEEK_END:
                abs_offset = PRIVATE (a_this)->in_buf_size - 1 - a_pos;
                break;

        default:
                return CR_BAD_PARAM_ERROR;
        }

        if ((abs_offset > 0)
            && (gulong) abs_offset < PRIVATE (a_this)->nb_bytes) {

                /*update the input stream's internal state */
                PRIVATE (a_this)->next_byte_index = abs_offset + 1;

                return CR_OK;
        }

        return CR_OUT_OF_BOUNDS_ERROR;
}

Project: libcroco

cr_input_get_cur_index (CRInput const * a_this, glong * a_index)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && a_index, CR_BAD_PARAM_ERROR);

        *a_index = PRIVATE (a_this)->next_byte_index;

        return CR_OK;
}

Project: libcroco

cr_input_new_real (void)
{
        CRInput *result = NULL;

        result = g_try_malloc (sizeof (CRInput));
        if (!result) {
                cr_utils_trace_info ("Out of memory");
                return NULL;
        }
        memset (result, 0, sizeof (CRInput));

        PRIVATE (result) = g_try_malloc (sizeof (CRInputPriv));
        if (!PRIVATE (result)) {
                cr_utils_trace_info ("Out of memory");
                g_free (result);
                return NULL;
        }
        memset (PRIVATE (result), 0, sizeof (CRInputPriv));
        PRIVATE (result)->free_in_buf = TRUE;
        return result;
}

Project: libcroco

cr_input_consume_chars (CRInput * a_this, guint32 a_char, gulong * a_nb_char)
{
        enum CRStatus status = CR_OK;
        gulong nb_consumed = 0;

        g_return_val_if_fail (a_this && PRIVATE (a_this) && a_nb_char,
                              CR_BAD_PARAM_ERROR);

        g_return_val_if_fail (a_char != 0 || a_nb_char != NULL,
                              CR_BAD_PARAM_ERROR);

        for (nb_consumed = 0; ((status == CR_OK)
                               && (*a_nb_char > 0
                                   && nb_consumed < *a_nb_char));
             nb_consumed++) {
                status = cr_input_consume_char (a_this, a_char);
        }

        *a_nb_char = nb_consumed;

        if ((nb_consumed > 0)
            && ((status == CR_PARSING_ERROR)
                || (status == CR_END_OF_INPUT_ERROR))) {
                status = CR_OK;
        }

        return status;
}

Project: libcroco

cr_input_get_cur_byte_addr (CRInput * a_this, guchar ** a_offset)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this) && a_offset,
                              CR_BAD_PARAM_ERROR);

        if (!PRIVATE (a_this)->next_byte_index) {
                return CR_START_OF_INPUT_ERROR;
        }

        *a_offset = cr_input_get_byte_addr
                (a_this, PRIVATE (a_this)->next_byte_index - 1);

        return CR_OK;
}

Project: libcroco

cr_input_ref (CRInput * a_this)
{
        g_return_if_fail (a_this && PRIVATE (a_this));

        PRIVATE (a_this)->ref_count++;
}

Project: libcroco

cr_input_consume_char (CRInput * a_this, guint32 a_char)
{
        guint32 c;
        enum CRStatus status;

        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        if ((status = cr_input_peek_char (a_this, &c)) != CR_OK) {
                return status;
        }

        if (c == a_char || a_char == 0) {
                status = cr_input_read_char (a_this, &c);
        } else {
                return CR_PARSING_ERROR;
        }

        return status;
}

Project: libcroco

cr_input_set_end_of_file (CRInput * a_this, gboolean a_eof)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        PRIVATE (a_this)->end_of_input = a_eof;

        return CR_OK;
}

Project: libcroco

cr_input_set_column_num (CRInput * a_this, glong a_col)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        PRIVATE (a_this)->col = a_col;

        return CR_OK;
}

Project: libcroco

cr_input_increment_col_num (CRInput * a_this, glong a_increment)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        PRIVATE (a_this)->col += a_increment;

        return CR_OK;
}

Project: libcroco

cr_input_get_end_of_file (CRInput const * a_this, gboolean * a_eof)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && a_eof, CR_BAD_PARAM_ERROR);

        *a_eof = PRIVATE (a_this)->end_of_input;

        return CR_OK;
}

Project: libcroco

cr_input_new_from_uri (const gchar * a_file_uri, enum CREncoding a_enc)
{
        CRInput *result = NULL;
        enum CRStatus status = CR_OK;
        FILE *file_ptr = NULL;
        guchar tmp_buf[CR_INPUT_MEM_CHUNK_SIZE] = { 0 };
        gulong nb_read = 0,
                len = 0,
                buf_size = 0;
        gboolean loop = TRUE;
        guchar *buf = NULL;

        g_return_val_if_fail (a_file_uri, NULL);

        file_ptr = fopen (a_file_uri, "r");

        if (file_ptr == NULL) {

#ifdef CR_DEBUG
                cr_utils_trace_debug ("could not open file");
#endif
                g_warning ("Could not open file %s\n", a_file_uri);

                return NULL;
        }

        /*load the file */
        while (loop) {
                nb_read = fread (tmp_buf, 1 /*read bytes */ ,
                                 CR_INPUT_MEM_CHUNK_SIZE /*nb of bytes */ ,
                                 file_ptr);

                if (nb_read != CR_INPUT_MEM_CHUNK_SIZE) {
                        /*we read less chars than we wanted */
                        if (feof (file_ptr)) {
                                /*we reached eof */
                                loop = FALSE;
                        } else {
                                /*a pb occurred !! */
                                cr_utils_trace_debug ("an io error occurred");
                                status = CR_ERROR;
                                goto cleanup;
                        }
                }

                if (status == CR_OK) {
                        /*read went well */
                        buf = g_realloc (buf, len + CR_INPUT_MEM_CHUNK_SIZE);
                        memcpy (buf + len, tmp_buf, nb_read);
                        len += nb_read;
                        buf_size += CR_INPUT_MEM_CHUNK_SIZE;
                }
        }

        if (status == CR_OK) {
                result = cr_input_new_from_buf (buf, len, a_enc, TRUE);
                if (!result) {
                        goto cleanup;
                }
                /*
                 *we should  free buf here because it's own by CRInput.
                 *(see the last parameter of cr_input_new_from_buf().
                 */
                buf = NULL;
        }

 cleanup:
        if (file_ptr) {
                fclose (file_ptr);
                file_ptr = NULL;
        }

        if (buf) {
                g_free (buf);
                buf = NULL;
        }

        return result;
}

Project: libcroco

cr_input_set_end_of_line (CRInput * a_this, gboolean a_eol)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        PRIVATE (a_this)->end_of_line = a_eol;

        return CR_OK;
}

Project: libcroco

cr_input_set_cur_pos (CRInput * a_this, CRInputPos const * a_pos)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this) && a_pos,
                              CR_BAD_PARAM_ERROR);

        cr_input_set_column_num (a_this, a_pos->col);
        cr_input_set_line_num (a_this, a_pos->line);
        cr_input_set_cur_index (a_this, a_pos->next_byte_index);
        cr_input_set_end_of_line (a_this, a_pos->end_of_line);
        cr_input_set_end_of_file (a_this, a_pos->end_of_file);

        return CR_OK;
}

Project: libcroco

cr_input_unref (CRInput * a_this)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), FALSE);

        if (PRIVATE (a_this)->ref_count) {
                PRIVATE (a_this)->ref_count--;
        }

        if (PRIVATE (a_this)->ref_count == 0) {
                cr_input_destroy (a_this);
                return TRUE;
        }
        return FALSE;
}

Project: libcroco

cr_input_get_end_of_line (CRInput const * a_this, gboolean * a_eol)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && a_eol, CR_BAD_PARAM_ERROR);

        *a_eol = PRIVATE (a_this)->end_of_line;

        return CR_OK;
}

Project: libcroco

cr_input_get_parsing_location (CRInput const *a_this,
                               CRParsingLocation *a_loc)
{
        g_return_val_if_fail (a_this 
                              && PRIVATE (a_this)
                              && a_loc, 
                              CR_BAD_PARAM_ERROR) ;

        a_loc->line = PRIVATE (a_this)->line ;
        a_loc->column = PRIVATE (a_this)->col ;
        if (PRIVATE (a_this)->next_byte_index) {
                a_loc->byte_offset = PRIVATE (a_this)->next_byte_index - 1 ;
        } else {
                a_loc->byte_offset = PRIVATE (a_this)->next_byte_index  ;
        }
        return CR_OK ;
}

Project: libcroco

cr_input_get_byte_addr (CRInput * a_this, gulong a_offset)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), NULL);

        if (a_offset >= PRIVATE (a_this)->nb_bytes) {
                return NULL;
        }

        return &PRIVATE (a_this)->in_buf[a_offset];
}

Project: libcroco

cr_input_read_byte (CRInput * a_this, guchar * a_byte)
{
        gulong nb_bytes_left = 0;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && a_byte, CR_BAD_PARAM_ERROR);

        g_return_val_if_fail (PRIVATE (a_this)->next_byte_index <=
                              PRIVATE (a_this)->nb_bytes, CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->end_of_input == TRUE)
                return CR_END_OF_INPUT_ERROR;

        nb_bytes_left = cr_input_get_nb_bytes_left (a_this);

        if (nb_bytes_left < 1) {
                return CR_END_OF_INPUT_ERROR;
        }

        *a_byte = PRIVATE (a_this)->in_buf[PRIVATE (a_this)->next_byte_index];

        if (PRIVATE (a_this)->nb_bytes -
            PRIVATE (a_this)->next_byte_index < 2) {
                PRIVATE (a_this)->end_of_input = TRUE;
        } else {
                PRIVATE (a_this)->next_byte_index++;
        }

        return CR_OK;
}

Project: libcroco

cr_input_increment_line_num (CRInput * a_this, glong a_increment)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        PRIVATE (a_this)->line += a_increment;

        return CR_OK;
}

Project: libcroco

cr_input_peek_char (CRInput const * a_this, guint32 * a_char)
{
        enum CRStatus status = CR_OK;
        gulong consumed = 0,
                nb_bytes_left = 0;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && a_char, CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->next_byte_index >=
            PRIVATE (a_this)->in_buf_size) {
                return CR_END_OF_INPUT_ERROR;
        }

        nb_bytes_left = cr_input_get_nb_bytes_left (a_this);

        if (nb_bytes_left < 1) {
                return CR_END_OF_INPUT_ERROR;
        }

        status = cr_utils_read_char_from_utf8_buf
                (PRIVATE (a_this)->in_buf +
                 PRIVATE (a_this)->next_byte_index,
                 nb_bytes_left, a_char, &consumed);

        return status;
}

Project: libcroco

cr_input_get_line_num (CRInput const * a_this, glong * a_line_num)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && a_line_num, CR_BAD_PARAM_ERROR);

        *a_line_num = PRIVATE (a_this)->line;

        return CR_OK;
}

Project: libcroco

cr_input_peek_byte (CRInput const * a_this, enum CRSeekPos a_origin,
                    gulong a_offset, guchar * a_byte)
{
        gulong abs_offset = 0;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && a_byte, CR_BAD_PARAM_ERROR);

        switch (a_origin) {

        case CR_SEEK_CUR:
                abs_offset = PRIVATE (a_this)->next_byte_index - 1 + a_offset;
                break;

        case CR_SEEK_BEGIN:
                abs_offset = a_offset;
                break;

        case CR_SEEK_END:
                abs_offset = PRIVATE (a_this)->in_buf_size - 1 - a_offset;
                break;

        default:
                return CR_BAD_PARAM_ERROR;
        }

        if (abs_offset < PRIVATE (a_this)->in_buf_size) {

                *a_byte = PRIVATE (a_this)->in_buf[abs_offset];

                return CR_OK;

        } else {
                return CR_END_OF_INPUT_ERROR;
        }
}

Project: libcroco

cr_input_consume_white_spaces (CRInput * a_this, gulong * a_nb_chars)
{
        enum CRStatus status = CR_OK;
        guint32 cur_char = 0,
                nb_consumed = 0;

        g_return_val_if_fail (a_this && PRIVATE (a_this) && a_nb_chars,
                              CR_BAD_PARAM_ERROR);

        for (nb_consumed = 0;
             ((*a_nb_chars > 0) && (nb_consumed < *a_nb_chars));
             nb_consumed++) {
                status = cr_input_peek_char (a_this, &cur_char);
                if (status != CR_OK)
                        break;

                /*if the next char is a white space, consume it ! */
                if (cr_utils_is_white_space (cur_char) == TRUE) {
                        status = cr_input_read_char (a_this, &cur_char);
                        if (status != CR_OK)
                                break;
                        continue;
                }

                break;

        }

	*a_nb_chars = (gulong) nb_consumed;

        if (nb_consumed && status == CR_END_OF_INPUT_ERROR) {
                status = CR_OK;
        }

        return status;
}

Project: libcroco

cr_input_get_nb_bytes_left (CRInput const * a_this)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), -1);
        g_return_val_if_fail (PRIVATE (a_this)->nb_bytes
                              <= PRIVATE (a_this)->in_buf_size, -1);
        g_return_val_if_fail (PRIVATE (a_this)->next_byte_index
                              <= PRIVATE (a_this)->nb_bytes, -1);

        if (PRIVATE (a_this)->end_of_input)
                return 0;

        return PRIVATE (a_this)->nb_bytes - PRIVATE (a_this)->next_byte_index;
}

Project: libcroco

cr_input_destroy (CRInput * a_this)
{
        if (a_this == NULL)
                return;

        if (PRIVATE (a_this)) {
                if (PRIVATE (a_this)->in_buf && PRIVATE (a_this)->free_in_buf) {
                        g_free (PRIVATE (a_this)->in_buf);
                        PRIVATE (a_this)->in_buf = NULL;
                }

                g_free (PRIVATE (a_this));
                PRIVATE (a_this) = NULL;
        }

        g_free (a_this);
}

Project: libcroco

cr_input_get_column_num (CRInput const * a_this, glong * a_col)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this) && a_col,
                              CR_BAD_PARAM_ERROR);

        *a_col = PRIVATE (a_this)->col;

        return CR_OK;
}

Project: libcroco

cr_input_end_of_input (CRInput const * a_this, gboolean * a_end_of_input)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && a_end_of_input, CR_BAD_PARAM_ERROR);

        *a_end_of_input = (PRIVATE (a_this)->next_byte_index
                           >= PRIVATE (a_this)->in_buf_size) ? TRUE : FALSE;

        return CR_OK;
}

Project: libcroco

cr_input_set_line_num (CRInput * a_this, glong a_line_num)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        PRIVATE (a_this)->line = a_line_num;

        return CR_OK;
}

Project: libcroco

cr_input_read_char (CRInput * a_this, guint32 * a_char)
{
        enum CRStatus status = CR_OK;
        gulong consumed = 0,
                nb_bytes_left = 0;

        g_return_val_if_fail (a_this && PRIVATE (a_this) && a_char,
                              CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->end_of_input == TRUE)
                return CR_END_OF_INPUT_ERROR;

        nb_bytes_left = cr_input_get_nb_bytes_left (a_this);

        if (nb_bytes_left < 1) {
                return CR_END_OF_INPUT_ERROR;
        }

        status = cr_utils_read_char_from_utf8_buf
                (PRIVATE (a_this)->in_buf
                 +
                 PRIVATE (a_this)->next_byte_index,
                 nb_bytes_left, a_char, &consumed);

        if (status == CR_OK) {
                /*update next byte index */
                PRIVATE (a_this)->next_byte_index += consumed;

                /*update line and column number */
                if (PRIVATE (a_this)->end_of_line == TRUE) {
                        PRIVATE (a_this)->col = 1;
                        PRIVATE (a_this)->line++;
                        PRIVATE (a_this)->end_of_line = FALSE;
                } else if (*a_char != '\n') {
                        PRIVATE (a_this)->col++;
                }

                if (*a_char == '\n') {
                        PRIVATE (a_this)->end_of_line = TRUE;
                }
        }

        return status;
}