Commit message: None
CVE: CVE-2018-15908
Vulnerability description: In Artifex Ghostscript 9.23 before 2018-08-23, attackers are able to supply malicious PostScript files to bypass .tempfile restrictions and write files.

Project: ghostscript

check_file_permissions(i_ctx_t *i_ctx_p, const char *fname, int len,
                        gx_io_device *iodev, const char *permitgroup)
{
    char fname_reduced[gp_file_name_sizeof];
    uint rlen = sizeof(fname_reduced);

    if (gp_file_name_reduce(fname, len, fname_reduced, &rlen) != gp_combine_success)
        return gs_error_invalidaccess;         /* fail if we couldn't reduce */
    return check_file_permissions_reduced(i_ctx_p, fname_reduced, rlen, iodev, permitgroup);
}

Project: ghostscript

execfile_cleanup(i_ctx_t *i_ctx_p)
{
    check_ostack(1);
    *++osp = esp[2];
    return zclosefile(i_ctx_p);
}

Project: ghostscript

execfile_finish(i_ctx_t *i_ctx_p)
{
    check_ostack(1);
    esp -= 2;
    execfile_cleanup(i_ctx_p);
    return o_pop_estack;
}

Project: ghostscript

file_cleanup(i_ctx_t *i_ctx_p)
{
    gx_io_device *iodev = r_ptr(esp + 2, gx_io_device);

    iodev->procs.enumerate_close(r_ptr(esp + 5, file_enum));
    return 0;
}

Project: ghostscript

file_continue(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    es_ptr pscratch = esp - 2;
    file_enum *pfen = r_ptr(esp - 1, file_enum);
    int devlen = esp[-3].value.intval;
    gx_io_device *iodev = r_ptr(esp - 4, gx_io_device);
    uint len = r_size(pscratch);
    uint code;

    if (len < devlen)
        return_error(gs_error_rangecheck);     /* not even room for device len */

    do {
        memcpy((char *)pscratch->value.bytes, iodev->dname, devlen);
        code = iodev->procs.enumerate_next(pfen, (char *)pscratch->value.bytes + devlen,
                    len - devlen);
        if (code == ~(uint) 0) {    /* all done */
            esp -= 5;               /* pop proc, pfen, devlen, iodev , mark */
            return o_pop_estack;
        } else if (code > len)      /* overran string */
            return_error(gs_error_rangecheck);
        else if (iodev != iodev_default(imemory)
              || (check_file_permissions(i_ctx_p, (char *)pscratch->value.bytes, code + devlen, iodev, "PermitFileReading")) == 0) {
            push(1);
            ref_assign(op, pscratch);
            r_set_size(op, code + devlen);
            push_op_estack(file_continue);  /* come again */
            *++esp = pscratch[2];   /* proc */
            return o_push_estack;
        }
    } while(1);
}

Project: ghostscript

file_is_tempfile(i_ctx_t *i_ctx_p, const uchar *fname, int len)
{
    ref *SAFETY;
    ref *tempfiles;
    ref kname;

    if (dict_find_string(systemdict, "SAFETY", &SAFETY) <= 0 ||
            dict_find_string(SAFETY, "tempfiles", &tempfiles) <= 0)
        return false;
    if (name_ref(imemory, fname, len, &kname, -1) < 0 ||
            dict_find(tempfiles, &kname, &SAFETY) <= 0)
        return false;
    return true;
}

Project: ghostscript

make_invalid_file(i_ctx_t *i_ctx_p, ref * fp)
{
    make_file(fp, avm_invalid_file_entry, ~0, i_ctx_p->invalid_file_stream);
}

Project: ghostscript

prefix_is_simple(const char *pstr)
{
    int i;
    char c;

    for (i = 0; (c = pstr[i]) != 0; i++) {
        if (!(c == '-' || c == '_' || (c >= '0' && c <= '9') ||
              (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')))
            return false;
    }
    return true;
}

Project: ghostscript

zdeletefile(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    gs_parsed_file_name_t pname;
    int code = parse_real_file_name(op, &pname, imemory, "deletefile");

    if (code < 0)
        return code;
    if (pname.iodev == iodev_default(imemory)) {
        if ((code = check_file_permissions(i_ctx_p, pname.fname, pname.len,
                pname.iodev, "PermitFileControl")) < 0 &&
                 !file_is_tempfile(i_ctx_p, op->value.bytes, r_size(op))) {
            return code;
        }
    }
    code = (*pname.iodev->procs.delete_file)(pname.iodev, pname.fname);
    gs_free_file_name(&pname, "deletefile");
    if (code < 0)
        return code;
    pop(1);
    return 0;
}

Project: ghostscript

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

    check_type_access(*op, t_file, a_executable | a_read | a_execute);
    check_estack(4);            /* cleanup, file, finish, file */
    push_mark_estack(es_other, execfile_cleanup);
    *++esp = *op;
    push_op_estack(execfile_finish);
    return zexec(i_ctx_p);
}

Project: ghostscript

zfile(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    char file_access[4];
    gs_parsed_file_name_t pname;
    int code = parse_file_access_string(op, file_access);
    stream *s;

    if (code < 0)
        return code;
    code = parse_file_name(op-1, &pname, i_ctx_p->LockFilePermissions, imemory);
    if (code < 0)
        return code;
        /*
         * HACK: temporarily patch the current context pointer into the
         * state pointer for stdio-related devices.  See ziodev.c for
         * more information.
         */
    if (pname.iodev && pname.iodev->dtype == iodev_dtype_stdio) {
        bool statement = (strcmp(pname.iodev->dname, "%statementedit%") == 0);
        bool lineedit = (strcmp(pname.iodev->dname, "%lineedit%") == 0);
        if (pname.fname)
            return_error(gs_error_invalidfileaccess);
        if (statement || lineedit) {
            /* These need special code to support callouts */
            gx_io_device *indev = gs_findiodevice(imemory,
                                                  (const byte *)"%stdin", 6);
            stream *ins;
            if (strcmp(file_access, "r"))
                return_error(gs_error_invalidfileaccess);
            indev->state = i_ctx_p;
            code = (indev->procs.open_device)(indev, file_access, &ins, imemory);
            indev->state = 0;
            if (code < 0)
                return code;
            check_ostack(2);
            push(2);
            make_stream_file(op - 3, ins, file_access);
            make_bool(op-2, statement);
            make_int(op-1, 0);
            make_string(op, icurrent_space, 0, NULL);
            return zfilelineedit(i_ctx_p);
        }
        pname.iodev->state = i_ctx_p;
        code = (*pname.iodev->procs.open_device)(pname.iodev,
                                                 file_access, &s, imemory);
        pname.iodev->state = NULL;
    } else {
        if (pname.iodev == NULL)
            pname.iodev = iodev_default(imemory);
        code = zopen_file(i_ctx_p, &pname, file_access, &s, imemory);
    }
    if (code < 0)
        return code;
    code = ssetfilename(s, op[-1].value.const_bytes, r_size(op - 1));
    if (code < 0) {
        sclose(s);
        return_error(gs_error_VMerror);
    }
    make_stream_file(op - 1, s, file_access);
    pop(1);
    return code;
}

Project: ghostscript

zfilenameforall(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    file_enum *pfen;
    gx_io_device *iodev = NULL;
    gs_parsed_file_name_t pname;
    int code = 0;

    check_write_type(*op, t_string);
    check_proc(op[-1]);
    check_read_type(op[-2], t_string);
    /* Push a mark, the iodev, devicenamelen, the scratch string, the enumerator, */
    /* and the procedure, and invoke the continuation. */
    check_estack(7);
    /* Get the iodevice */
    code = parse_file_name(op-2, &pname, i_ctx_p->LockFilePermissions, imemory);
    if (code < 0)
        return code;
    iodev = (pname.iodev == NULL) ? iodev_default(imemory) : pname.iodev;

    /* Check for several conditions that just cause us to return success */
    if (pname.len == 0 || iodev->procs.enumerate_files == iodev_no_enumerate_files) {
        pop(3);
        return 0;       /* no pattern, or device not found -- just return */
    }
    pfen = iodev->procs.enumerate_files(iodev, (const char *)pname.fname,
                pname.len, imemory);
    if (pfen == 0)
        return_error(gs_error_VMerror);
    push_mark_estack(es_for, file_cleanup);
    ++esp;
    make_istruct(esp, 0, iodev);
    ++esp;
    make_int(esp, r_size(op-2) - pname.len);
    *++esp = *op;
    ++esp;
    make_istruct(esp, 0, pfen);
    *++esp = op[-1];
    pop(3);
    code = file_continue(i_ctx_p);
    return (code == o_pop_estack ? o_push_estack : code);
}

Project: ghostscript

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

    push(1);
    make_const_string(op, avm_foreign | a_readonly, 1,
                      (const byte *)&gp_file_name_list_separator);
    return 0;
}

Project: ghostscript

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

    check_read_type(*op, t_string);
/****** NOT IMPLEMENTED YET ******/
    return_error(gs_error_undefined);
}

Project: ghostscript

zlibfile(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    int code;
    byte cname[DEFAULT_BUFFER_SIZE];
    uint clen;
    gs_parsed_file_name_t pname;
    stream *s;
    gx_io_device *iodev_dflt;

    check_ostack(2);
    code = parse_file_name(op, &pname, i_ctx_p->LockFilePermissions, imemory);
    if (code < 0)
        return code;
    iodev_dflt = iodev_default(imemory);
    if (pname.iodev == NULL)
        pname.iodev = iodev_dflt;
    if (pname.iodev != iodev_dflt) { /* Non-OS devices don't have search paths (yet). */
        code = zopen_file(i_ctx_p, &pname, "r", &s, imemory);
        if (code >= 0) {
            code = ssetfilename(s, op->value.const_bytes, r_size(op));
            if (code < 0) {
                sclose(s);
                return_error(gs_error_VMerror);
            }
        }
        if (code < 0) {
            push(1);
            make_false(op);
            return 0;
        }
        make_stream_file(op, s, "r");
    } else {
        ref fref;

        code = lib_file_open(i_ctx_p->lib_path, imemory, i_ctx_p, pname.fname, pname.len,
                             (char *)cname, sizeof(cname), &clen, &fref);
        if (code >= 0) {
            s = fptr(&fref);
            code = ssetfilename(s, cname, clen);
            if (code < 0) {
                sclose(s);
                return_error(gs_error_VMerror);
            }
        }
        if (code < 0) {
            if (code == gs_error_VMerror || code == gs_error_invalidfileaccess)
                return code;
            push(1);
            make_false(op);
            return 0;
        }
        ref_assign(op, &fref);
    }
    push(1);
    make_true(op);
    return 0;
}

Project: ghostscript

zrenamefile(i_ctx_t *i_ctx_p)
{
    int code;
    os_ptr op = osp;
    gs_parsed_file_name_t pname1, pname2;

    code = parse_real_file_name(op, &pname2, imemory, "renamefile(to)");
    if (code < 0)
        return code;

    pname1.fname = 0;
    code = parse_real_file_name(op - 1, &pname1, imemory, "renamefile(from)");
    if (code >= 0) {
        gx_io_device *iodev_dflt = iodev_default(imemory);
        if (pname1.iodev != pname2.iodev ) {
            if (pname1.iodev == iodev_dflt)
                pname1.iodev = pname2.iodev;
            if (pname2.iodev == iodev_dflt)
                pname2.iodev = pname1.iodev;
        }
        if (pname1.iodev != pname2.iodev ||
            (pname1.iodev == iodev_dflt &&
                /*
                 * We require FileControl permissions on the source path
                 * unless it is a temporary file. Also, we require FileControl
                 * and FileWriting permissions to the destination file/path.
                 */
              ((check_file_permissions(i_ctx_p, pname1.fname, pname1.len,
                                        pname1.iodev, "PermitFileControl") < 0 &&
                  !file_is_tempfile(i_ctx_p, op[-1].value.bytes, r_size(op - 1))) ||
              (check_file_permissions(i_ctx_p, pname2.fname, pname2.len,
                                        pname2.iodev, "PermitFileControl") < 0 ||
              check_file_permissions(i_ctx_p, pname2.fname, pname2.len,
                                        pname2.iodev, "PermitFileWriting") < 0 )))) {
            code = gs_note_error(gs_error_invalidfileaccess);
        } else {
            code = (*pname1.iodev->procs.rename_file)(pname1.iodev,
                            pname1.fname, pname2.fname);
        }
    }
    gs_free_file_name(&pname2, "renamefile(to)");
    gs_free_file_name(&pname1, "renamefile(from)");
    if (code < 0)
        return code;
    pop(2);
    return 0;
}

Project: ghostscript

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

    switch (r_type(op)) {
        case t_file:
            {
                stream *s;

                make_bool(op, (file_is_valid(s, op) ? 1 : 0));
            }
            return 0;
        case t_string:
            {
                gs_parsed_file_name_t pname;
                struct stat fstat;
                int code = parse_file_name(op, &pname,
                                           i_ctx_p->LockFilePermissions, imemory);
                if (code < 0) {
                    if (code == gs_error_undefinedfilename) {
                        make_bool(op, 0);
                        code = 0;
                    }
                    return code;
                }
                code = gs_terminate_file_name(&pname, imemory, "status");
                if (code < 0)
                    return code;
                if ((code = check_file_permissions(i_ctx_p, pname.fname, pname.len,
                                       pname.iodev, "PermitFileReading")) >= 0) {
                    code = (*pname.iodev->procs.file_status)(pname.iodev,
                                                       pname.fname, &fstat);
                }
                switch (code) {
                    case 0:
                        check_ostack(4);
                        /*
                         * Check to make sure that the file size fits into
                         * a PostScript integer.  (On some systems, long is
                         * 32 bits, but file sizes are 64 bits.)
                         */
                        push(4);
                        make_int(op - 4, stat_blocks(&fstat));
                        make_int(op - 3, fstat.st_size);
                        /*
                         * We can't check the value simply by using ==,
                         * because signed/unsigned == does the wrong thing.
                         * Instead, since integer assignment only keeps the
                         * bottom bits, we convert the values to double
                         * and then test for equality.  This handles all
                         * cases of signed/unsigned or width mismatch.
                         */
                        if ((double)op[-4].value.intval !=
                              (double)stat_blocks(&fstat) ||
                            (double)op[-3].value.intval !=
                              (double)fstat.st_size
                            )
                            return_error(gs_error_limitcheck);
                        make_int(op - 2, fstat.st_mtime);
                        make_int(op - 1, fstat.st_ctime);
                        make_bool(op, 1);
                        break;
                    case gs_error_undefinedfilename:
                        make_bool(op, 0);
                        code = 0;
                }
                gs_free_file_name(&pname, "status");
                return code;
            }
        default:
            return_op_typecheck(op);
    }
}