Commit message: Bug 702582, CVE 2020-15900 Memory Corruption in Ghostscript 9.52

Fix the 'rsearch' calculation for the 'post' size to give the correct
size.  Previous calculation would result in a size that was too large,
and could underflow to max uint32_t. Also fix 'rsearch' to return the
correct 'pre' string with empty string match.

A future change may 'undefine' this undocumented, non-standard operator
during initialization as we do with the many other non-standard internal
PostScript operators and procedures.
CVE: CVE-2020-15900
CWE: CWE-787
Vulnerability description: A memory corruption issue was found in Artifex Ghostscript 9.50 and 9.52. Use of a non-standard PostScript operator can allow overriding of file access controls. The 'rsearch' calculation for the 'post' size resulted in a size that was too large, and could underflow to max uint32_t. This was fixed in commit 5d499272b95a6b890a1397e11d20937de000d31b.

Project: ghostpdl
File: zstring.c

search_impl(i_ctx_t *i_ctx_p, bool forward)
{
    os_ptr op = osp;
    os_ptr op1 = op - 1;
    uint size = r_size(op);
    uint count;
    byte *pat;
    byte *ptr;
    byte ch;
    int incr = forward ? 1 : -1;

    check_read_type(*op1, t_string);
    check_read_type(*op, t_string);
    if (size > r_size(op1)) {	/* can't match */
        make_false(op);
        return 0;
    }
    count = r_size(op1) - size;
    ptr = op1->value.bytes;
    if (size == 0)
        goto found;
    if (!forward)
        ptr += count;
    pat = op->value.bytes;
    ch = pat[0];
    do {
        if (*ptr == ch && (size == 1 || !memcmp(ptr, pat, size)))
            goto found;
        ptr += incr;
    }
    while (count--);
    /* No match */
    make_false(op);
    return 0;
found:
    op->tas.type_attrs = op1->tas.type_attrs;
    op->value.bytes = ptr;				/* match */
    op->tas.rsize = size;				/* match */
    push(2);
    op[-1] = *op1;					/* pre */
    op[-3].value.bytes = ptr + size;			/* post */
    if (forward) {
        op[-1].tas.rsize = ptr - op[-1].value.bytes;	/* pre */
        op[-3].tas.rsize = count;			/* post */
    } else {
        op[-1].tas.rsize = count;			/* pre */
        op[-3].tas.rsize -= count + size;		/* post */
    }
    make_true(op);
    return 0;
}

Project: ghostpdl
File: zstring.c

zstring(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    byte *sbody;
    uint size;

    check_type(*op, t_integer);
    if (op->value.intval < 0 )
        return_error(gs_error_rangecheck);
    if (op->value.intval > max_string_size )
        return_error(gs_error_limitcheck); /* to match Distiller */
    size = op->value.intval;
    sbody = ialloc_string(size, "string");
    if (sbody == 0)
        return_error(gs_error_VMerror);
    make_string(op, a_all | icurrent_space, size, sbody);
    memset(sbody, 0, size);
    return 0;
}

Project: ghostpdl
File: zstring.c

znamestring(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;

    check_type(*op, t_name);
    name_string_ref(imemory, op, op);
    return 0;
}

Project: ghostpdl
File: zstring.c

zanchorsearch(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    os_ptr op1 = op - 1;
    uint size = r_size(op);

    check_read_type(*op, t_string);
    check_read_type(*op1, t_string);
    if (size <= r_size(op1) && !memcmp(op1->value.bytes, op->value.bytes, size)) {
        os_ptr op0 = op;

        push(1);
        *op0 = *op1;
        r_set_size(op0, size);
        op1->value.bytes += size;
        r_dec_size(op1, size);
        make_true(op);
    } else
        make_false(op);
    return 0;
}

Project: ghostpdl
File: zstring.c

zstringmatch(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    os_ptr op1 = op - 1;
    bool result;

    check_read_type(*op, t_string);
    switch (r_type(op1)) {
        case t_string:
            check_read(*op1);
            goto cmp;
        case t_name:
            name_string_ref(imemory, op1, op1);	/* can't fail */
cmp:
            result = string_match(op1->value.const_bytes, r_size(op1),
                                  op->value.const_bytes, r_size(op),
                                  NULL);
            break;
        default:
            result = (r_size(op) == 1 && *op->value.bytes == '*');
    }
    make_bool(op1, result);
    pop(1);
    return 0;
}

Project: ghostpdl
File: zstring.c

zsearch(i_ctx_t *i_ctx_p)
{
    return search_impl(i_ctx_p, true);
}

Project: ghostpdl
File: zstring.c

zbytestring(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    byte *sbody;
    uint size;

    check_int_leu(*op, max_int);
    size = (uint)op->value.intval;
    sbody = ialloc_bytes(size, ".bytestring");
    if (sbody == 0)
        return_error(gs_error_VMerror);
    make_astruct(op, a_all | icurrent_space, sbody);
    memset(sbody, 0, size);
    return 0;
}

Project: ghostpdl
File: zstring.c

zstringbreak(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    uint i, j;

    check_read_type(op[-1], t_string);
    check_read_type(*op, t_string);
    /* We can't use strpbrk here, because C doesn't allow nulls in strings. */
    for (i = 0; i < r_size(op - 1); ++i)
        for (j = 0; j < r_size(op); ++j)
            if (op[-1].value.const_bytes[i] == op->value.const_bytes[j]) {
                make_int(op - 1, i);
                goto done;
            }
    make_null(op - 1);
 done:
    pop(1);
    return 0;
}

Project: ghostpdl
File: zstring.c

zrsearch(i_ctx_t *i_ctx_p)
{
    return search_impl(i_ctx_p, false);
}