static bool ldb_dn_explode(struct ldb_dn *dn)
{
	char *p, *ex_name = NULL, *ex_value = NULL, *data, *d, *dt, *t;
	bool trim = true;
	bool in_extended = true;
	bool in_ex_name = false;
	bool in_ex_value = false;
	bool in_attr = false;
	bool in_value = false;
	bool in_quote = false;
	bool is_oid = false;
	bool escape = false;
	unsigned int x;
	size_t l = 0;
	int ret;
	char *parse_dn;
	bool is_index;

	if ( ! dn || dn->invalid) return false;

	if (dn->components) {
		return true;
	}

	if (dn->ext_linearized) {
		parse_dn = dn->ext_linearized;
	} else {
		parse_dn = dn->linearized;
	}

	if ( ! parse_dn ) {
		return false;
	}

	is_index = (strncmp(parse_dn, "DN=@INDEX:", 10) == 0);

	/* Empty DNs */
	if (parse_dn[0] == '\0') {
		return true;
	}

	/* Special DNs case */
	if (dn->special) {
		return true;
	}

	/* make sure we free this if allocated previously before replacing */
	LDB_FREE(dn->components);
	dn->comp_num = 0;

	LDB_FREE(dn->ext_components);
	dn->ext_comp_num = 0;

	/* in the common case we have 3 or more components */
	/* make sure all components are zeroed, other functions depend on it */
	dn->components = talloc_zero_array(dn, struct ldb_dn_component, 3);
	if ( ! dn->components) {
		return false;
	}

	/* Components data space is allocated here once */
	data = talloc_array(dn->components, char, strlen(parse_dn) + 1);
	if (!data) {
		return false;
	}

	p = parse_dn;
	t = NULL;
	d = dt = data;

	while (*p) {
		if (in_extended) {

			if (!in_ex_name && !in_ex_value) {

				if (p[0] == '<') {
					p++;
					ex_name = d;
					in_ex_name = true;
					continue;
				} else if (p[0] == '\0') {
					p++;
					continue;
				} else {
					in_extended = false;
					in_attr = true;
					dt = d;

					continue;
				}
			}

			if (in_ex_name && *p == '=') {
				*d++ = '\0';
				p++;
				ex_value = d;
				in_ex_name = false;
				in_ex_value = true;
				continue;
			}

			if (in_ex_value && *p == '>') {
				const struct ldb_dn_extended_syntax *ext_syntax;
				struct ldb_val ex_val = {
					.data = (uint8_t *)ex_value,
					.length = d - ex_value
				};

				*d++ = '\0';
				p++;
				in_ex_value = false;

				/* Process name and ex_value */

				dn->ext_components = talloc_realloc(dn,
								    dn->ext_components,
								    struct ldb_dn_ext_component,
								    dn->ext_comp_num + 1);
				if ( ! dn->ext_components) {
					/* ouch ! */
					goto failed;
				}

				ext_syntax = ldb_dn_extended_syntax_by_name(dn->ldb, ex_name);
				if (!ext_syntax) {
					/* We don't know about this type of extended DN */
					goto failed;
				}

				dn->ext_components[dn->ext_comp_num].name = talloc_strdup(dn->ext_components, ex_name);
				if (!dn->ext_components[dn->ext_comp_num].name) {
					/* ouch */
					goto failed;
				}
				ret = ext_syntax->read_fn(dn->ldb, dn->ext_components,
							  &ex_val, &dn->ext_components[dn->ext_comp_num].value);
				if (ret != LDB_SUCCESS) {
					ldb_dn_mark_invalid(dn);
					goto failed;
				}

				dn->ext_comp_num++;

				if (*p == '\0') {
					/* We have reached the end (extended component only)! */
					talloc_free(data);
					return true;

				} else if (*p == ';') {
					p++;
					continue;
				} else {
					ldb_dn_mark_invalid(dn);
					goto failed;
				}
			}

			*d++ = *p++;
			continue;
		}
		if (in_attr) {
			if (trim) {
				if (*p == ' ') {
					p++;
					continue;
				}

				/* first char */
				trim = false;

				if (!isascii(*p)) {
					/* attr names must be ascii only */
					ldb_dn_mark_invalid(dn);
					goto failed;
				}

				if (isdigit(*p)) {
					is_oid = true;
				} else
				if ( ! isalpha(*p)) {
					/* not a digit nor an alpha,
 					 * invalid attribute name */
					ldb_dn_mark_invalid(dn);
					goto failed;
				}

				/* Copy this character across from parse_dn,
				 * now we have trimmed out spaces */
				*d++ = *p++;
				continue;
			}

			if (*p == ' ') {
				p++;
				/* valid only if we are at the end */
				trim = true;
				continue;
			}

			if (trim && (*p != '=')) {
				/* spaces/tabs are not allowed */
				ldb_dn_mark_invalid(dn);
				goto failed;
			}

			if (*p == '=') {
				/* attribute terminated */
				in_attr = false;
				in_value = true;
				trim = true;
				l = 0;

				/* Terminate this string in d
				 * (which is a copy of parse_dn
				 *  with spaces trimmed) */
				*d++ = '\0';
				dn->components[dn->comp_num].name = talloc_strdup(dn->components, dt);
				if ( ! dn->components[dn->comp_num].name) {
					/* ouch */
					goto failed;
				}

				dt = d;

				p++;
				continue;
			}

			if (!isascii(*p)) {
				/* attr names must be ascii only */
				ldb_dn_mark_invalid(dn);
				goto failed;
			}

			if (is_oid && ( ! (isdigit(*p) || (*p == '.')))) {
				/* not a digit nor a dot,
				 * invalid attribute oid */
				ldb_dn_mark_invalid(dn);
				goto failed;
			} else
			if ( ! (isalpha(*p) || isdigit(*p) || (*p == '-'))) {
				/* not ALPHA, DIGIT or HYPHEN */
				ldb_dn_mark_invalid(dn);
				goto failed;
			}

			*d++ = *p++;
			continue;
		}

		if (in_value) {
			if (in_quote) {
				if (*p == '\"') {
					if (p[-1] != '\\') {
						p++;
						in_quote = false;
						continue;
					}
				}
				*d++ = *p++;
				l++;
				continue;
			}

			if (trim) {
				if (*p == ' ') {
					p++;
					continue;
				}

				/* first char */
				trim = false;

				if (*p == '\"') {
					in_quote = true;
					p++;
					continue;
				}
			}

			switch (*p) {

			/* TODO: support ber encoded values
			case '#':
			*/

			case ',':
				if (escape) {
					*d++ = *p++;
					l++;
					escape = false;
					continue;
				}
				/* ok found value terminator */

				if ( t ) {
					/* trim back */
					d -= (p - t);
					l -= (p - t);
				}

				in_attr = true;
				in_value = false;
				trim = true;

                                p++;
                                *d++ = '\0';
                               dn->components[dn->comp_num].value.data = \
                                       (uint8_t *)talloc_memdup(dn->components, dt, l + 1);
                                dn->components[dn->comp_num].value.length = l;
                                if ( ! dn->components[dn->comp_num].value.data) {
                                        /* ouch ! */
                                        goto failed;
                                }
                               talloc_set_name_const(dn->components[dn->comp_num].value.data,
                                                     (const char *)dn->components[dn->comp_num].value.data);

                                dt = d;

									dn->components,
									struct ldb_dn_component,
									dn->comp_num + 1);
					if ( ! dn->components) {
						/* ouch ! */
						goto failed;
					}
					/* make sure all components are zeroed, other functions depend on this */
					memset(&dn->components[dn->comp_num], '\0', sizeof(struct ldb_dn_component));
				}

				continue;

			case '+':
			case '=':
				/* to main compatibility with earlier
				versions of ldb indexing, we have to
				accept the base64 encoded binary index
				values, which contain a '+' or '='
				which should normally be escaped */
				if (is_index) {
					if ( t ) t = NULL;
					*d++ = *p++;
					l++;
					break;
				}
				/* fall through */
			case '\"':
			case '<':
			case '>':
			case ';':
				/* a string with not escaped specials is invalid (tested) */
				if ( ! escape) {
					ldb_dn_mark_invalid(dn);
					goto failed;
				}
				escape = false;

				*d++ = *p++;
				l++;

				if ( t ) t = NULL;
				break;

			case '\\':
				if ( ! escape) {
					escape = true;
					p++;
					continue;
				}
				escape = false;

				*d++ = *p++;
				l++;

				if ( t ) t = NULL;
				break;

			default:
				if (escape) {
					if (isxdigit(p[0]) && isxdigit(p[1])) {
						if (sscanf(p, "%02x", &x) != 1) {
							/* invalid escaping sequence */
							ldb_dn_mark_invalid(dn);
							goto failed;
						}
						p += 2;
						*d++ = (unsigned char)x;
					} else {
						*d++ = *p++;
					}

					escape = false;
					l++;
					if ( t ) t = NULL;
					break;
				}

				if (*p == ' ') {
					if ( ! t) t = p;
				} else {
					if ( t ) t = NULL;
				}

				*d++ = *p++;
				l++;

				break;
			}

		}
	}


gs_call_interp(i_ctx_t **pi_ctx_p, ref * pref, int user_errors,
               int *pexit_code, ref * perror_object)
{
    ref *epref = pref;
    ref doref;
    ref *perrordict;
    ref error_name;
    int code, ccode;
    ref saref;
    i_ctx_t *i_ctx_p = *pi_ctx_p;
    int *gc_signal = &imemory_system->gs_lib_ctx->gcsignal;

    *pexit_code = 0;
    *gc_signal = 0;
    ialloc_reset_requested(idmemory);
again:
    /* Avoid a dangling error object that might get traced by a future GC. */
    make_null(perror_object);
    o_stack.requested = e_stack.requested = d_stack.requested = 0;
    while (*gc_signal) { /* Some routine below triggered a GC. */
        gs_gc_root_t epref_root;

        *gc_signal = 0;
        /* Make sure that doref will get relocated properly if */
        /* a garbage collection happens with epref == &doref. */
        gs_register_ref_root(imemory_system, &epref_root,
                             (void **)&epref, "gs_call_interp(epref)");
        code = interp_reclaim(pi_ctx_p, -1);
        i_ctx_p = *pi_ctx_p;
        gs_unregister_root(imemory_system, &epref_root,
                           "gs_call_interp(epref)");
        if (code < 0)
            return code;
    }
    code = interp(pi_ctx_p, epref, perror_object);
    i_ctx_p = *pi_ctx_p;
    if (!r_has_type(&i_ctx_p->error_object, t__invalid)) {
        *perror_object = i_ctx_p->error_object;
        make_t(&i_ctx_p->error_object, t__invalid);
    }
    /* Prevent a dangling reference to the GC signal in ticks_left */
    /* in the frame of interp, but be prepared to do a GC if */
    /* an allocation in this routine asks for it. */
    *gc_signal = 0;
    set_gc_signal(i_ctx_p, 1);
    if (esp < esbot)            /* popped guard entry */
        esp = esbot;
    switch (code) {
        case gs_error_Fatal:
            *pexit_code = 255;
            return code;
        case gs_error_Quit:
            *perror_object = osp[-1];
            *pexit_code = code = osp->value.intval;
            osp -= 2;
            return
                (code == 0 ? gs_error_Quit :
                 code < 0 && code > -100 ? code : gs_error_Fatal);
        case gs_error_InterpreterExit:
            return 0;
        case gs_error_ExecStackUnderflow:
/****** WRONG -- must keep mark blocks intact ******/
            ref_stack_pop_block(&e_stack);
            doref = *perror_object;
            epref = &doref;
            goto again;
        case gs_error_VMreclaim:
            /* Do the GC and continue. */
            /* We ignore the return value here, if it fails here
             * we'll call it again having jumped to the "again" label.
             * Where, assuming it fails again, we'll handle the error.
             */
            (void)interp_reclaim(pi_ctx_p,
                                  (osp->value.intval == 2 ?
                                   avm_global : avm_local));
            i_ctx_p = *pi_ctx_p;
            make_oper(&doref, 0, zpop);
            epref = &doref;
            goto again;
        case gs_error_NeedInput:
        case gs_error_interrupt:
            return code;
    }
    /* Adjust osp in case of operand stack underflow */
    if (osp < osbot - 1)
        osp = osbot - 1;
    /* We have to handle stack over/underflow specially, because */
    /* we might be able to recover by adding or removing a block. */
    switch (code) {
        case gs_error_dictstackoverflow:
            /* We don't have to handle this specially: */
            /* The only places that could generate it */
            /* use check_dstack, which does a ref_stack_extend, */
            /* so if` we get this error, it's a real one. */
            if (osp >= ostop) {
                if ((ccode = ref_stack_extend(&o_stack, 1)) < 0)
                    return ccode;
            }
            /* Skip system dictionaries for CET 20-02-02 */
            ccode = copy_stack(i_ctx_p, &d_stack, min_dstack_size, &saref);
            if (ccode < 0)
                return ccode;
            ref_stack_pop_to(&d_stack, min_dstack_size);
            dict_set_top();
            *++osp = saref;
            break;
        case gs_error_dictstackunderflow:
            if (ref_stack_pop_block(&d_stack) >= 0) {
                dict_set_top();
                doref = *perror_object;
                epref = &doref;
                goto again;
            }
            break;
        case gs_error_execstackoverflow:
            /* We don't have to handle this specially: */
            /* The only places that could generate it */
            /* use check_estack, which does a ref_stack_extend, */
            /* so if we get this error, it's a real one. */
            if (osp >= ostop) {
                if ((ccode = ref_stack_extend(&o_stack, 1)) < 0)
                    return ccode;
            }
            ccode = copy_stack(i_ctx_p, &e_stack, 0, &saref);
            if (ccode < 0)
                return ccode;
            {
                uint count = ref_stack_count(&e_stack);
                uint limit = ref_stack_max_count(&e_stack) - ES_HEADROOM;

                if (count > limit) {
                    /*
                     * If there is an e-stack mark within MIN_BLOCK_ESTACK of
                     * the new top, cut the stack back to remove the mark.
                     */
                    int skip = count - limit;
                    int i;

                    for (i = skip; i < skip + MIN_BLOCK_ESTACK; ++i) {
                        const ref *ep = ref_stack_index(&e_stack, i);

                        if (r_has_type_attrs(ep, t_null, a_executable)) {
                            skip = i + 1;
                            break;
                        }
                    }
                    pop_estack(i_ctx_p, skip);
                }
            }
            *++osp = saref;
            break;
        case gs_error_stackoverflow:
            if (ref_stack_extend(&o_stack, o_stack.requested) >= 0) {   /* We can't just re-execute the object, because */
                /* it might be a procedure being pushed as a */
                /* literal.  We check for this case specially. */
                doref = *perror_object;
                if (r_is_proc(&doref)) {
                    *++osp = doref;
                    make_null_proc(&doref);
                }
                epref = &doref;
                goto again;
            }
            ccode = copy_stack(i_ctx_p, &o_stack, 0, &saref);
            if (ccode < 0)
                return ccode;
            ref_stack_clear(&o_stack);
            *++osp = saref;
            break;
        case gs_error_stackunderflow:
            if (ref_stack_pop_block(&o_stack) >= 0) {
                doref = *perror_object;
                epref = &doref;
                goto again;
            }
            break;
    }
    if (user_errors < 0)
        return code;
    if (gs_errorname(i_ctx_p, code, &error_name) < 0)
        return code;            /* out-of-range error code! */

    /*  We refer to gserrordict first, which is not accessible to Postcript jobs
     *  If we're running with SAFERERRORS all the handlers are copied to gserrordict
     *  so we'll always find the default one. If not SAFERERRORS, only gs specific
     *  errors are in gserrordict.
     */
    if (dict_find_string(systemdict, "gserrordict", &perrordict) <= 0 ||
        (dict_find(perrordict, &error_name, &epref) <= 0 &&
         (dict_find_string(systemdict, "errordict", &perrordict) <= 0 ||
          dict_find(perrordict, &error_name, &epref) <= 0))
        )
        return code;            /* error name not in errordict??? */

    doref = *epref;
     epref = &doref;
     /* Push the error object on the operand stack if appropriate. */
     if (!GS_ERROR_IS_INTERRUPT(code)) {
         /* Replace the error object if within an oparray or .errorexec. */
         osp++;
         if (osp >= ostop) {
        }
        *osp = *perror_object;
         }
         *osp = *perror_object;
         errorexec_find(i_ctx_p, osp);
        /* If using SAFER, hand a name object to the error handler, rather than the executable
         * object/operator itself.
         */
        if (i_ctx_p->LockFilePermissions) {
             code = obj_cvs(imemory, osp, buf + 2, 256, &rlen, (const byte **)&bufptr);
             if (code < 0) {
                 const char *unknownstr = "--unknown--";
                 rlen = strlen(unknownstr);
                 memcpy(buf, unknownstr, rlen);
             }
             else {
                buf[0] = buf[1] = buf[rlen + 2] = buf[rlen + 3] = '-';
                rlen += 4;
             }
            code = name_ref(imemory, buf, rlen, osp, 1);
            if (code < 0)
                make_null(osp);
         }
     }

Artifex Ghostscript 9.25 and earlier allows attackers to bypass a sandbox protection mechanism via vectors involving errorhandler setup. NOTE: this issue exists because of an incomplete fix for CVE-2018-17183.


gs_call_interp(i_ctx_t **pi_ctx_p, ref * pref, int user_errors,
               int *pexit_code, ref * perror_object)
{
    ref *epref = pref;
    ref doref;
    ref *perrordict;
    ref error_name;
    int code, ccode;
    ref saref;
    i_ctx_t *i_ctx_p = *pi_ctx_p;
    int *gc_signal = &imemory_system->gs_lib_ctx->gcsignal;

    *pexit_code = 0;
    *gc_signal = 0;
    ialloc_reset_requested(idmemory);
again:
    /* Avoid a dangling error object that might get traced by a future GC. */
    make_null(perror_object);
    o_stack.requested = e_stack.requested = d_stack.requested = 0;
    while (*gc_signal) { /* Some routine below triggered a GC. */
        gs_gc_root_t epref_root;

        *gc_signal = 0;
        /* Make sure that doref will get relocated properly if */
        /* a garbage collection happens with epref == &doref. */
        gs_register_ref_root(imemory_system, &epref_root,
                             (void **)&epref, "gs_call_interp(epref)");
        code = interp_reclaim(pi_ctx_p, -1);
        i_ctx_p = *pi_ctx_p;
        gs_unregister_root(imemory_system, &epref_root,
                           "gs_call_interp(epref)");
        if (code < 0)
            return code;
    }
    code = interp(pi_ctx_p, epref, perror_object);
    i_ctx_p = *pi_ctx_p;
    if (!r_has_type(&i_ctx_p->error_object, t__invalid)) {
        *perror_object = i_ctx_p->error_object;
        make_t(&i_ctx_p->error_object, t__invalid);
    }
    /* Prevent a dangling reference to the GC signal in ticks_left */
    /* in the frame of interp, but be prepared to do a GC if */
    /* an allocation in this routine asks for it. */
    *gc_signal = 0;
    set_gc_signal(i_ctx_p, 1);
    if (esp < esbot)            /* popped guard entry */
        esp = esbot;
    switch (code) {
        case gs_error_Fatal:
            *pexit_code = 255;
            return code;
        case gs_error_Quit:
            *perror_object = osp[-1];
            *pexit_code = code = osp->value.intval;
            osp -= 2;
            return
                (code == 0 ? gs_error_Quit :
                 code < 0 && code > -100 ? code : gs_error_Fatal);
        case gs_error_InterpreterExit:
            return 0;
        case gs_error_ExecStackUnderflow:
/****** WRONG -- must keep mark blocks intact ******/
            ref_stack_pop_block(&e_stack);
            doref = *perror_object;
            epref = &doref;
            goto again;
        case gs_error_VMreclaim:
            /* Do the GC and continue. */
            /* We ignore the return value here, if it fails here
             * we'll call it again having jumped to the "again" label.
             * Where, assuming it fails again, we'll handle the error.
             */
            (void)interp_reclaim(pi_ctx_p,
                                  (osp->value.intval == 2 ?
                                   avm_global : avm_local));
            i_ctx_p = *pi_ctx_p;
            make_oper(&doref, 0, zpop);
            epref = &doref;
            goto again;
        case gs_error_NeedInput:
        case gs_error_interrupt:
            return code;
    }
    /* Adjust osp in case of operand stack underflow */
    if (osp < osbot - 1)
        osp = osbot - 1;
    /* We have to handle stack over/underflow specially, because */
    /* we might be able to recover by adding or removing a block. */
    switch (code) {
        case gs_error_dictstackoverflow:
            /* We don't have to handle this specially: */
            /* The only places that could generate it */
            /* use check_dstack, which does a ref_stack_extend, */
            /* so if` we get this error, it's a real one. */
            if (osp >= ostop) {
                if ((ccode = ref_stack_extend(&o_stack, 1)) < 0)
                    return ccode;
            }
            /* Skip system dictionaries for CET 20-02-02 */
            ccode = copy_stack(i_ctx_p, &d_stack, min_dstack_size, &saref);
            if (ccode < 0)
                return ccode;
            ref_stack_pop_to(&d_stack, min_dstack_size);
            dict_set_top();
            *++osp = saref;
            break;
        case gs_error_dictstackunderflow:
            if (ref_stack_pop_block(&d_stack) >= 0) {
                dict_set_top();
                doref = *perror_object;
                epref = &doref;
                goto again;
            }
            break;
        case gs_error_execstackoverflow:
            /* We don't have to handle this specially: */
            /* The only places that could generate it */
            /* use check_estack, which does a ref_stack_extend, */
            /* so if we get this error, it's a real one. */
            if (osp >= ostop) {
                if ((ccode = ref_stack_extend(&o_stack, 1)) < 0)
                    return ccode;
            }
            ccode = copy_stack(i_ctx_p, &e_stack, 0, &saref);
            if (ccode < 0)
                return ccode;
            {
                uint count = ref_stack_count(&e_stack);
                uint limit = ref_stack_max_count(&e_stack) - ES_HEADROOM;

                if (count > limit) {
                    /*
                     * If there is an e-stack mark within MIN_BLOCK_ESTACK of
                     * the new top, cut the stack back to remove the mark.
                     */
                    int skip = count - limit;
                    int i;

                    for (i = skip; i < skip + MIN_BLOCK_ESTACK; ++i) {
                        const ref *ep = ref_stack_index(&e_stack, i);

                        if (r_has_type_attrs(ep, t_null, a_executable)) {
                            skip = i + 1;
                            break;
                        }
                    }
                    pop_estack(i_ctx_p, skip);
                }
            }
            *++osp = saref;
            break;
        case gs_error_stackoverflow:
            if (ref_stack_extend(&o_stack, o_stack.requested) >= 0) {   /* We can't just re-execute the object, because */
                /* it might be a procedure being pushed as a */
                /* literal.  We check for this case specially. */
                doref = *perror_object;
                if (r_is_proc(&doref)) {
                    *++osp = doref;
                    make_null_proc(&doref);
                }
                epref = &doref;
                goto again;
            }
            ccode = copy_stack(i_ctx_p, &o_stack, 0, &saref);
            if (ccode < 0)
                return ccode;
            ref_stack_clear(&o_stack);
            *++osp = saref;
            break;
        case gs_error_stackunderflow:
            if (ref_stack_pop_block(&o_stack) >= 0) {
                doref = *perror_object;
                epref = &doref;
                goto again;
            }
            break;
    }
    if (user_errors < 0)
        return code;
    if (gs_errorname(i_ctx_p, code, &error_name) < 0)
        return code;            /* out-of-range error code! */

    /*  We refer to gserrordict first, which is not accessible to Postcript jobs
     *  If we're running with SAFERERRORS all the handlers are copied to gserrordict
     *  so we'll always find the default one. If not SAFERERRORS, only gs specific
     *  errors are in gserrordict.
     */
    if (dict_find_string(systemdict, "gserrordict", &perrordict) <= 0 ||
        (dict_find(perrordict, &error_name, &epref) <= 0 &&
         (dict_find_string(systemdict, "errordict", &perrordict) <= 0 ||
          dict_find(perrordict, &error_name, &epref) <= 0))
        )
        return code;            /* error name not in errordict??? */

    doref = *epref;
     epref = &doref;
     /* Push the error object on the operand stack if appropriate. */
     if (!GS_ERROR_IS_INTERRUPT(code)) {
        byte buf[260], *bufptr;
        uint rlen;
         /* Replace the error object if within an oparray or .errorexec. */
         osp++;
         if (osp >= ostop) {
        }
        *osp = *perror_object;
         }
         *osp = *perror_object;
         errorexec_find(i_ctx_p, osp);

        if (!r_has_type(osp, t_string) && !r_has_type(osp, t_name)) {
             code = obj_cvs(imemory, osp, buf + 2, 256, &rlen, (const byte **)&bufptr);
             if (code < 0) {
                 const char *unknownstr = "--unknown--";
                 rlen = strlen(unknownstr);
                 memcpy(buf, unknownstr, rlen);
                bufptr = buf;
             }
             else {
                ref *tobj;
                bufptr[rlen] = '\0';
                /* Only pass a name object if the operator doesn't exist in systemdict
                 * i.e. it's an internal operator we have hidden
                 */
                code = dict_find_string(systemdict, (const char *)bufptr, &tobj);
                if (code < 0) {
                    buf[0] = buf[1] = buf[rlen + 2] = buf[rlen + 3] = '-';
                    rlen += 4;
                    bufptr = buf;
                }
                else {
                    bufptr = NULL;
                }
            }
            if (bufptr) {
                code = name_ref(imemory, buf, rlen, osp, 1);
                if (code < 0)
                    make_null(osp);
             }
         }
     }


CVE-2015-5252: s3: smbd: Fix symlink verification (file access outside the share).

Ensure matching component ends in '/' or '\0'.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=11395

Signed-off-by: Jeremy Allison <jra@samba.org>
Reviewed-by: Volker Lendecke <vl@samba.org>

NTSTATUS check_reduced_name_with_privilege(connection_struct *conn,
			const char *fname,
			struct smb_request *smbreq)
{
	NTSTATUS status;
	TALLOC_CTX *ctx = talloc_tos();
	const char *conn_rootdir;
	size_t rootdir_len;
	char *dir_name = NULL;
	const char *last_component = NULL;
	char *resolved_name = NULL;
	char *saved_dir = NULL;
        struct smb_filename *smb_fname_cwd = NULL;
        struct privilege_paths *priv_paths = NULL;
        int ret;

        DEBUG(3,("check_reduced_name_with_privilege [%s] [%s]\n",
                        fname,

	priv_paths = talloc_zero(smbreq, struct privilege_paths);
	if (!priv_paths) {
		status = NT_STATUS_NO_MEMORY;
		goto err;
	}

	if (!parent_dirname(ctx, fname, &dir_name, &last_component)) {
		status = NT_STATUS_NO_MEMORY;
		goto err;
	}

	priv_paths->parent_name.base_name = talloc_strdup(priv_paths, dir_name);
	priv_paths->file_name.base_name = talloc_strdup(priv_paths, last_component);

	if (priv_paths->parent_name.base_name == NULL ||
			priv_paths->file_name.base_name == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto err;
	}

	if (SMB_VFS_STAT(conn, &priv_paths->parent_name) != 0) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}
	/* Remember where we were. */
	saved_dir = vfs_GetWd(ctx, conn);
	if (!saved_dir) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	/* Go to the parent directory to lock in memory. */
	if (vfs_ChDir(conn, priv_paths->parent_name.base_name) == -1) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	/* Get the absolute path of the parent directory. */
	resolved_name = SMB_VFS_REALPATH(conn,".");
	if (!resolved_name) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	if (*resolved_name != '/') {
		DEBUG(0,("check_reduced_name_with_privilege: realpath "
			"doesn't return absolute paths !\n"));
		status = NT_STATUS_OBJECT_NAME_INVALID;
		goto err;
	}

	DEBUG(10,("check_reduced_name_with_privilege: realpath [%s] -> [%s]\n",
		priv_paths->parent_name.base_name,
		resolved_name));

	/* Now check the stat value is the same. */
	smb_fname_cwd = synthetic_smb_fname(talloc_tos(), ".", NULL, NULL);
	if (smb_fname_cwd == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto err;
	}

	if (SMB_VFS_LSTAT(conn, smb_fname_cwd) != 0) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	/* Ensure we're pointing at the same place. */
	if (!check_same_stat(&smb_fname_cwd->st, &priv_paths->parent_name.st)) {
		DEBUG(0,("check_reduced_name_with_privilege: "
			"device/inode/uid/gid on directory %s changed. "
			"Denying access !\n",
			priv_paths->parent_name.base_name));
		status = NT_STATUS_ACCESS_DENIED;
		goto err;
	}

	/* Ensure we're below the connect path. */

	conn_rootdir = SMB_VFS_CONNECTPATH(conn, fname);
	if (conn_rootdir == NULL) {
		DEBUG(2, ("check_reduced_name_with_privilege: Could not get "
			"conn_rootdir\n"));
		status = NT_STATUS_ACCESS_DENIED;
		goto err;
	}
        }

vfs.c in smbd in Samba 3.x and 4.x before 4.1.22, 4.2.x before 4.2.7, and 4.3.x before 4.3.3, when share names with certain substring relationships exist, allows remote attackers to bypass intended file-access restrictions via a symlink that points outside of a share.


NTSTATUS check_reduced_name_with_privilege(connection_struct *conn,
			const char *fname,
			struct smb_request *smbreq)
{
	NTSTATUS status;
	TALLOC_CTX *ctx = talloc_tos();
	const char *conn_rootdir;
	size_t rootdir_len;
	char *dir_name = NULL;
	const char *last_component = NULL;
	char *resolved_name = NULL;
	char *saved_dir = NULL;
        struct smb_filename *smb_fname_cwd = NULL;
        struct privilege_paths *priv_paths = NULL;
        int ret;
       bool matched;

        DEBUG(3,("check_reduced_name_with_privilege [%s] [%s]\n",
                        fname,

	priv_paths = talloc_zero(smbreq, struct privilege_paths);
	if (!priv_paths) {
		status = NT_STATUS_NO_MEMORY;
		goto err;
	}

	if (!parent_dirname(ctx, fname, &dir_name, &last_component)) {
		status = NT_STATUS_NO_MEMORY;
		goto err;
	}

	priv_paths->parent_name.base_name = talloc_strdup(priv_paths, dir_name);
	priv_paths->file_name.base_name = talloc_strdup(priv_paths, last_component);

	if (priv_paths->parent_name.base_name == NULL ||
			priv_paths->file_name.base_name == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto err;
	}

	if (SMB_VFS_STAT(conn, &priv_paths->parent_name) != 0) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}
	/* Remember where we were. */
	saved_dir = vfs_GetWd(ctx, conn);
	if (!saved_dir) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	/* Go to the parent directory to lock in memory. */
	if (vfs_ChDir(conn, priv_paths->parent_name.base_name) == -1) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	/* Get the absolute path of the parent directory. */
	resolved_name = SMB_VFS_REALPATH(conn,".");
	if (!resolved_name) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	if (*resolved_name != '/') {
		DEBUG(0,("check_reduced_name_with_privilege: realpath "
			"doesn't return absolute paths !\n"));
		status = NT_STATUS_OBJECT_NAME_INVALID;
		goto err;
	}

	DEBUG(10,("check_reduced_name_with_privilege: realpath [%s] -> [%s]\n",
		priv_paths->parent_name.base_name,
		resolved_name));

	/* Now check the stat value is the same. */
	smb_fname_cwd = synthetic_smb_fname(talloc_tos(), ".", NULL, NULL);
	if (smb_fname_cwd == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto err;
	}

	if (SMB_VFS_LSTAT(conn, smb_fname_cwd) != 0) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	/* Ensure we're pointing at the same place. */
	if (!check_same_stat(&smb_fname_cwd->st, &priv_paths->parent_name.st)) {
		DEBUG(0,("check_reduced_name_with_privilege: "
			"device/inode/uid/gid on directory %s changed. "
			"Denying access !\n",
			priv_paths->parent_name.base_name));
		status = NT_STATUS_ACCESS_DENIED;
		goto err;
	}

	/* Ensure we're below the connect path. */

	conn_rootdir = SMB_VFS_CONNECTPATH(conn, fname);
	if (conn_rootdir == NULL) {
		DEBUG(2, ("check_reduced_name_with_privilege: Could not get "
			"conn_rootdir\n"));
		status = NT_STATUS_ACCESS_DENIED;
		goto err;
	}
        }


plan_a (char const *filename)
{
  char const *s;
  char const *lim;
  char const **ptr;
  char *buffer;
  lin iline;
  size_t size = instat.st_size;

  /* Fail if the file size doesn't fit in a size_t,
     or if storage isn't available.  */
  if (! (size == instat.st_size
	 && (buffer = malloc (size ? size : (size_t) 1))))
    return false;

  /* Read the input file, but don't bother reading it if it's empty.
     When creating files, the files do not actually exist.  */
  if (size)
     {
       if (S_ISREG (instat.st_mode))
         {
	  int ifd = safe_open (filename, O_RDONLY|binary_transput, 0);
 	  size_t buffered = 0, n;
 	  if (ifd < 0)
 	    pfatal ("can't open file %s", quotearg (filename));

		  /* Some non-POSIX hosts exaggerate st_size in text mode;
		     or the file may have shrunk!  */
		  size = buffered;
		  break;
		}
	      if (n == (size_t) -1)
		{
		  /* Perhaps size is too large for this host.  */
		  close (ifd);
		  free (buffer);
		  return false;
		}
	      buffered += n;
	    }

	  if (close (ifd) != 0)
	    read_fatal ();
	}

In GNU patch through 2.7.6, the following of symlinks is mishandled in certain cases other than input files. This affects inp.c and util.c.


plan_a (char const *filename)
{
  char const *s;
  char const *lim;
  char const **ptr;
  char *buffer;
  lin iline;
  size_t size = instat.st_size;

  /* Fail if the file size doesn't fit in a size_t,
     or if storage isn't available.  */
  if (! (size == instat.st_size
	 && (buffer = malloc (size ? size : (size_t) 1))))
    return false;

  /* Read the input file, but don't bother reading it if it's empty.
     When creating files, the files do not actually exist.  */
  if (size)
     {
       if (S_ISREG (instat.st_mode))
         {
	  int flags = O_RDONLY | binary_transput;
 	  size_t buffered = 0, n;
	  int ifd;

	  if (! follow_symlinks)
	    flags |= O_NOFOLLOW;
	  ifd = safe_open (filename, flags, 0);
 	  if (ifd < 0)
 	    pfatal ("can't open file %s", quotearg (filename));

		  /* Some non-POSIX hosts exaggerate st_size in text mode;
		     or the file may have shrunk!  */
		  size = buffered;
		  break;
		}
	      if (n == (size_t) -1)
		{
		  /* Perhaps size is too large for this host.  */
		  close (ifd);
		  free (buffer);
		  return false;
		}
	      buffered += n;
	    }

	  if (close (ifd) != 0)
	    read_fatal ();
	}


_dbus_header_byteswap: change the first byte of the message, not just the struct member

This has been wrong approximately forever, for instance see:
http://lists.freedesktop.org/archives/dbus/2007-March/007357.html

Bug: https://bugs.freedesktop.org/show_bug.cgi?id=38120
Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=629938
Reviewed-by: Will Thompson <will.thompson@collabora.co.uk>

 _dbus_header_byteswap (DBusHeader *header,
                        int         new_order)
 {
   if (header->byte_order == new_order)
     return;

   _dbus_marshal_byteswap (&_dbus_header_signature_str,
                           0, header->byte_order,
                           new_order,
                           &header->data, 0);

   header->byte_order = new_order;
 }

The _dbus_header_byteswap function in dbus-marshal-header.c in D-Bus (aka DBus) 1.2.x before 1.2.28, 1.4.x before 1.4.12, and 1.5.x before 1.5.4 does not properly handle a non-native byte order, which allows local users to cause a denial of service (connection loss), obtain potentially sensitive information, or conduct unspecified state-modification attacks via crafted messages.


 _dbus_header_byteswap (DBusHeader *header,
                        int         new_order)
 {
  unsigned char byte_order;

   if (header->byte_order == new_order)
     return;

  byte_order = _dbus_string_get_byte (&header->data, BYTE_ORDER_OFFSET);
  _dbus_assert (header->byte_order == byte_order);

   _dbus_marshal_byteswap (&_dbus_header_signature_str,
                           0, header->byte_order,
                           new_order,
                           &header->data, 0);

  _dbus_string_set_byte (&header->data, BYTE_ORDER_OFFSET, new_order);
   header->byte_order = new_order;
 }


 gs_nulldevice(gs_gstate * pgs)
 {
     int code = 0;
     if (pgs->device == 0 || !gx_device_is_null(pgs->device)) {
         gx_device *ndev;
         code = gs_copydevice(&ndev, (const gx_device *)&gs_null_device,
                                 pgs->memory);

         if (code < 0)
             return code;
         /*
          * Internal devices have a reference count of 0, not 1,
          * aside from references from graphics states.
           to sort out how the icc profile is best handled with this device.
           It seems to inherit properties from the current device if there
           is one */
        rc_init(ndev, pgs->memory, 0);
        if (pgs->device != NULL) {
            if ((code = dev_proc(pgs->device, get_profile)(pgs->device,
                                               &(ndev->icc_struct))) < 0)
                return code;
            rc_increment(ndev->icc_struct);
            set_dev_proc(ndev, get_profile, gx_default_get_profile);
        }

        if ((code = gs_setdevice_no_erase(pgs, ndev)) < 0)

         if ((code = gs_setdevice_no_erase(pgs, ndev)) < 0)
             gs_free_object(pgs->memory, ndev, "gs_copydevice(device)");
     }
     return code;
 }

It was found that RHSA-2018:2918 did not fully fix CVE-2018-16509. An attacker could possibly exploit another variant of the flaw and bypass the -dSAFER protection to, for example, execute arbitrary shell commands via a specially crafted PostScript document. This only affects ghostscript 9.07 as shipped with Red Hat Enterprise Linux 7.


 gs_nulldevice(gs_gstate * pgs)
 {
     int code = 0;
    bool saveLockSafety = false;
     if (pgs->device == 0 || !gx_device_is_null(pgs->device)) {
         gx_device *ndev;
         code = gs_copydevice(&ndev, (const gx_device *)&gs_null_device,
                                 pgs->memory);

         if (code < 0)
             return code;
        if (gs_currentdevice_inline(pgs) != NULL)
            saveLockSafety = gs_currentdevice_inline(pgs)->LockSafetyParams;
         /*
          * Internal devices have a reference count of 0, not 1,
          * aside from references from graphics states.
           to sort out how the icc profile is best handled with this device.
           It seems to inherit properties from the current device if there
           is one */
        rc_init(ndev, pgs->memory, 0);
        if (pgs->device != NULL) {
            if ((code = dev_proc(pgs->device, get_profile)(pgs->device,
                                               &(ndev->icc_struct))) < 0)
                return code;
            rc_increment(ndev->icc_struct);
            set_dev_proc(ndev, get_profile, gx_default_get_profile);
        }

        if ((code = gs_setdevice_no_erase(pgs, ndev)) < 0)

         if ((code = gs_setdevice_no_erase(pgs, ndev)) < 0)
             gs_free_object(pgs->memory, ndev, "gs_copydevice(device)");
        gs_currentdevice_inline(pgs)->LockSafetyParams = saveLockSafety;
     }
     return code;
 }


zrestore(i_ctx_t *i_ctx_p)
 {
     os_ptr op = osp;
    alloc_save_t *asave;
    bool last;
    vm_save_t *vmsave;
    int code = restore_check_operand(op, &asave, idmemory);

     if (code < 0)
         return code;
     if_debug2m('u', imemory, "[u]vmrestore 0x%lx, id = %lu\n",
               (ulong) alloc_save_client_data(asave),
                (ulong) op->value.saveid);
     if (I_VALIDATE_BEFORE_RESTORE)
         ivalidate_clean_spaces(i_ctx_p);
        ivalidate_clean_spaces(i_ctx_p);
    /* Check the contents of the stacks. */
     {
         int code;

        if ((code = restore_check_stack(i_ctx_p, &o_stack, asave, false)) < 0 ||
            (code = restore_check_stack(i_ctx_p, &e_stack, asave, true)) < 0 ||
            (code = restore_check_stack(i_ctx_p, &d_stack, asave, false)) < 0
             ) {
             osp++;
             return code;
         }
     }
     /* Reset l_new in all stack entries if the new save level is zero. */
     /* Also do some special fixing on the e-stack. */
     restore_fix_stack(i_ctx_p, &o_stack, asave, false);
    }


zrestore(i_ctx_t *i_ctx_p)
restore_check_save(i_ctx_t *i_ctx_p, alloc_save_t **asave)
 {
     os_ptr op = osp;
    int code = restore_check_operand(op, asave, idmemory);

     if (code < 0)
         return code;
     if_debug2m('u', imemory, "[u]vmrestore 0x%lx, id = %lu\n",
               (ulong) alloc_save_client_data(*asave),
                (ulong) op->value.saveid);
     if (I_VALIDATE_BEFORE_RESTORE)
         ivalidate_clean_spaces(i_ctx_p);
        ivalidate_clean_spaces(i_ctx_p);
    /* Check the contents of the stacks. */
     {
         int code;

        if ((code = restore_check_stack(i_ctx_p, &o_stack, *asave, false)) < 0 ||
            (code = restore_check_stack(i_ctx_p, &e_stack, *asave, true)) < 0 ||
            (code = restore_check_stack(i_ctx_p, &d_stack, *asave, false)) < 0
             ) {
             osp++;
             return code;
         }
     }
    osp++;
    return 0;
}

/* the semantics of restore differ slightly between Level 1 and
   Level 2 and later - the latter includes restoring the device
   state (whilst Level 1 didn't have "page devices" as such).
   Hence we have two restore operators - one here (Level 1)
   and one in zdevice2.c (Level 2+). For that reason, the
   operand checking and guts of the restore operation are
   separated so both implementations can use them to best
   effect.
 */
int
dorestore(i_ctx_t *i_ctx_p, alloc_save_t *asave)
{
    os_ptr op = osp;
    bool last;
    vm_save_t *vmsave;
    int code;

    osp--;

     /* Reset l_new in all stack entries if the new save level is zero. */
     /* Also do some special fixing on the e-stack. */
     restore_fix_stack(i_ctx_p, &o_stack, asave, false);
    }


libnm-util: remove some left-over debugging code

destroy_one_secret (gpointer data)
{
 	char *secret = (char *) data;

 	/* Don't leave the secret lying around in memory */
g_message ("%s: destroying %s", __func__, secret);
 	memset (secret, 0, strlen (secret));
 	g_free (secret);
 }

The destroy_one_secret function in nm-setting-vpn.c in libnm-util in the NetworkManager package 0.8.999-3.git20110526 in Fedora 15 creates a log entry containing a certificate password, which allows local users to obtain sensitive information by reading a log file.


destroy_one_secret (gpointer data)
{
 	char *secret = (char *) data;

 	/* Don't leave the secret lying around in memory */
 	memset (secret, 0, strlen (secret));
 	g_free (secret);
 }


PatternMatch(char *pat, int patdashes, char *string, int stringdashes)
{
    char        c,
                t;

    if (stringdashes < patdashes)
	return 0;
    for (;;) {
	switch (c = *pat++) {
	case '*':
	    if (!(c = *pat++))
		return 1;
	    if (c == XK_minus) {
		patdashes--;
		for (;;) {
		    while ((t = *string++) != XK_minus)
			if (!t)
			    return 0;
		    stringdashes--;
		    if (PatternMatch(pat, patdashes, string, stringdashes))
			return 1;
		    if (stringdashes == patdashes)
			return 0;
		}
	    } else {
		for (;;) {
		    while ((t = *string++) != c) {
			if (!t)
			    return 0;
			if (t == XK_minus) {
			    if (stringdashes-- < patdashes)
				return 0;
			}
		    }
		    if (PatternMatch(pat, patdashes, string, stringdashes))
			return 1;
 		}
 	    }
 	case '?':
	    if (*string++ == XK_minus)
 		stringdashes--;
 	    break;
 	case '\0':
 	    return (*string == '\0');
		patdashes--;
		stringdashes--;
		break;
	    }
	    return 0;
	default:
	    if (c == *string++)
		break;
	    return 0;
	}
    }

In the PatternMatch function in fontfile/fontdir.c in libXfont through 1.5.2 and 2.x before 2.0.2, an attacker with access to an X connection can cause a buffer over-read during pattern matching of fonts, leading to information disclosure or a crash (denial of service). This occurs because '\0' characters are incorrectly skipped in situations involving ? characters.


PatternMatch(char *pat, int patdashes, char *string, int stringdashes)
{
    char        c,
                t;

    if (stringdashes < patdashes)
	return 0;
    for (;;) {
	switch (c = *pat++) {
	case '*':
	    if (!(c = *pat++))
		return 1;
	    if (c == XK_minus) {
		patdashes--;
		for (;;) {
		    while ((t = *string++) != XK_minus)
			if (!t)
			    return 0;
		    stringdashes--;
		    if (PatternMatch(pat, patdashes, string, stringdashes))
			return 1;
		    if (stringdashes == patdashes)
			return 0;
		}
	    } else {
		for (;;) {
		    while ((t = *string++) != c) {
			if (!t)
			    return 0;
			if (t == XK_minus) {
			    if (stringdashes-- < patdashes)
				return 0;
			}
		    }
		    if (PatternMatch(pat, patdashes, string, stringdashes))
			return 1;
 		}
 	    }
 	case '?':
	    if ((t = *string++) == XK_minus)
 		stringdashes--;
	    if (!t)
		return 0;
 	    break;
 	case '\0':
 	    return (*string == '\0');
		patdashes--;
		stringdashes--;
		break;
	    }
	    return 0;
	default:
	    if (c == *string++)
		break;
	    return 0;
	}
    }


gs_call_interp(i_ctx_t **pi_ctx_p, ref * pref, int user_errors,
               int *pexit_code, ref * perror_object)
{
    ref *epref = pref;
    ref doref;
    ref *perrordict;
    ref error_name;
    int code, ccode;
    ref saref;
    i_ctx_t *i_ctx_p = *pi_ctx_p;
    int *gc_signal = &imemory_system->gs_lib_ctx->gcsignal;

    *pexit_code = 0;
    *gc_signal = 0;
    ialloc_reset_requested(idmemory);
again:
    /* Avoid a dangling error object that might get traced by a future GC. */
    make_null(perror_object);
    o_stack.requested = e_stack.requested = d_stack.requested = 0;
    while (*gc_signal) { /* Some routine below triggered a GC. */
        gs_gc_root_t epref_root;

        *gc_signal = 0;
        /* Make sure that doref will get relocated properly if */
        /* a garbage collection happens with epref == &doref. */
        gs_register_ref_root(imemory_system, &epref_root,
                             (void **)&epref, "gs_call_interp(epref)");
        code = interp_reclaim(pi_ctx_p, -1);
        i_ctx_p = *pi_ctx_p;
        gs_unregister_root(imemory_system, &epref_root,
                           "gs_call_interp(epref)");
        if (code < 0)
            return code;
    }
    code = interp(pi_ctx_p, epref, perror_object);
    i_ctx_p = *pi_ctx_p;
    if (!r_has_type(&i_ctx_p->error_object, t__invalid)) {
        *perror_object = i_ctx_p->error_object;
        make_t(&i_ctx_p->error_object, t__invalid);
    }
    /* Prevent a dangling reference to the GC signal in ticks_left */
    /* in the frame of interp, but be prepared to do a GC if */
    /* an allocation in this routine asks for it. */
    *gc_signal = 0;
    set_gc_signal(i_ctx_p, 1);
    if (esp < esbot)            /* popped guard entry */
        esp = esbot;
    switch (code) {
        case gs_error_Fatal:
            *pexit_code = 255;
            return code;
        case gs_error_Quit:
            *perror_object = osp[-1];
            *pexit_code = code = osp->value.intval;
            osp -= 2;
            return
                (code == 0 ? gs_error_Quit :
                 code < 0 && code > -100 ? code : gs_error_Fatal);
        case gs_error_InterpreterExit:
            return 0;
        case gs_error_ExecStackUnderflow:
/****** WRONG -- must keep mark blocks intact ******/
            ref_stack_pop_block(&e_stack);
            doref = *perror_object;
            epref = &doref;
            goto again;
        case gs_error_VMreclaim:
            /* Do the GC and continue. */
            /* We ignore the return value here, if it fails here
             * we'll call it again having jumped to the "again" label.
             * Where, assuming it fails again, we'll handle the error.
             */
            (void)interp_reclaim(pi_ctx_p,
                                  (osp->value.intval == 2 ?
                                   avm_global : avm_local));
            i_ctx_p = *pi_ctx_p;
            make_oper(&doref, 0, zpop);
            epref = &doref;
            goto again;
        case gs_error_NeedInput:
        case gs_error_interrupt:
            return code;
    }
    /* Adjust osp in case of operand stack underflow */
    if (osp < osbot - 1)
        osp = osbot - 1;
    /* We have to handle stack over/underflow specially, because */
    /* we might be able to recover by adding or removing a block. */
    switch (code) {
        case gs_error_dictstackoverflow:
            /* We don't have to handle this specially: */
            /* The only places that could generate it */
            /* use check_dstack, which does a ref_stack_extend, */
            /* so if` we get this error, it's a real one. */
            if (osp >= ostop) {
                if ((ccode = ref_stack_extend(&o_stack, 1)) < 0)
                    return ccode;
            }
            /* Skip system dictionaries for CET 20-02-02 */
            ccode = copy_stack(i_ctx_p, &d_stack, min_dstack_size, &saref);
            if (ccode < 0)
                return ccode;
            ref_stack_pop_to(&d_stack, min_dstack_size);
            dict_set_top();
            *++osp = saref;
            break;
        case gs_error_dictstackunderflow:
            if (ref_stack_pop_block(&d_stack) >= 0) {
                dict_set_top();
                doref = *perror_object;
                epref = &doref;
                goto again;
            }
            break;
        case gs_error_execstackoverflow:
            /* We don't have to handle this specially: */
            /* The only places that could generate it */
            /* use check_estack, which does a ref_stack_extend, */
            /* so if we get this error, it's a real one. */
            if (osp >= ostop) {
                if ((ccode = ref_stack_extend(&o_stack, 1)) < 0)
                    return ccode;
            }
            ccode = copy_stack(i_ctx_p, &e_stack, 0, &saref);
            if (ccode < 0)
                return ccode;
            {
                uint count = ref_stack_count(&e_stack);
                uint limit = ref_stack_max_count(&e_stack) - ES_HEADROOM;

                if (count > limit) {
                    /*
                     * If there is an e-stack mark within MIN_BLOCK_ESTACK of
                     * the new top, cut the stack back to remove the mark.
                     */
                    int skip = count - limit;
                    int i;

                    for (i = skip; i < skip + MIN_BLOCK_ESTACK; ++i) {
                        const ref *ep = ref_stack_index(&e_stack, i);

                        if (r_has_type_attrs(ep, t_null, a_executable)) {
                            skip = i + 1;
                            break;
                        }
                    }
                    pop_estack(i_ctx_p, skip);
                }
            }
            *++osp = saref;
            break;
        case gs_error_stackoverflow:
            if (ref_stack_extend(&o_stack, o_stack.requested) >= 0) {   /* We can't just re-execute the object, because */
                /* it might be a procedure being pushed as a */
                /* literal.  We check for this case specially. */
                doref = *perror_object;
                if (r_is_proc(&doref)) {
                    *++osp = doref;
                    make_null_proc(&doref);
                }
                epref = &doref;
                goto again;
            }
            ccode = copy_stack(i_ctx_p, &o_stack, 0, &saref);
            if (ccode < 0)
                return ccode;
            ref_stack_clear(&o_stack);
            *++osp = saref;
            break;
        case gs_error_stackunderflow:
            if (ref_stack_pop_block(&o_stack) >= 0) {
                doref = *perror_object;
                epref = &doref;
                goto again;
            }
            break;
    }
    if (user_errors < 0)
        return code;
    if (gs_errorname(i_ctx_p, code, &error_name) < 0)
        return code;            /* out-of-range error code! */
    /*
     * For greater Adobe compatibility, only the standard PostScript errors
     * are defined in errordict; the rest are in gserrordict.
     */
    if (dict_find_string(systemdict, "errordict", &perrordict) <= 0 ||
        (dict_find(perrordict, &error_name, &epref) <= 0 &&
         (dict_find_string(systemdict, "gserrordict", &perrordict) <= 0 ||
          dict_find(perrordict, &error_name, &epref) <= 0))
        )
        return code;            /* error name not in errordict??? */
    doref = *epref;
    epref = &doref;
     /* Push the error object on the operand stack if appropriate. */
     if (!GS_ERROR_IS_INTERRUPT(code)) {
         /* Replace the error object if within an oparray or .errorexec. */
        *++osp = *perror_object;
         errorexec_find(i_ctx_p, osp);
     }
     goto again;
}

In Artifex Ghostscript before 9.24, attackers able to supply crafted PostScript files could use insufficient interpreter stack-size checking during error handling to crash the interpreter.


gs_call_interp(i_ctx_t **pi_ctx_p, ref * pref, int user_errors,
               int *pexit_code, ref * perror_object)
{
    ref *epref = pref;
    ref doref;
    ref *perrordict;
    ref error_name;
    int code, ccode;
    ref saref;
    i_ctx_t *i_ctx_p = *pi_ctx_p;
    int *gc_signal = &imemory_system->gs_lib_ctx->gcsignal;

    *pexit_code = 0;
    *gc_signal = 0;
    ialloc_reset_requested(idmemory);
again:
    /* Avoid a dangling error object that might get traced by a future GC. */
    make_null(perror_object);
    o_stack.requested = e_stack.requested = d_stack.requested = 0;
    while (*gc_signal) { /* Some routine below triggered a GC. */
        gs_gc_root_t epref_root;

        *gc_signal = 0;
        /* Make sure that doref will get relocated properly if */
        /* a garbage collection happens with epref == &doref. */
        gs_register_ref_root(imemory_system, &epref_root,
                             (void **)&epref, "gs_call_interp(epref)");
        code = interp_reclaim(pi_ctx_p, -1);
        i_ctx_p = *pi_ctx_p;
        gs_unregister_root(imemory_system, &epref_root,
                           "gs_call_interp(epref)");
        if (code < 0)
            return code;
    }
    code = interp(pi_ctx_p, epref, perror_object);
    i_ctx_p = *pi_ctx_p;
    if (!r_has_type(&i_ctx_p->error_object, t__invalid)) {
        *perror_object = i_ctx_p->error_object;
        make_t(&i_ctx_p->error_object, t__invalid);
    }
    /* Prevent a dangling reference to the GC signal in ticks_left */
    /* in the frame of interp, but be prepared to do a GC if */
    /* an allocation in this routine asks for it. */
    *gc_signal = 0;
    set_gc_signal(i_ctx_p, 1);
    if (esp < esbot)            /* popped guard entry */
        esp = esbot;
    switch (code) {
        case gs_error_Fatal:
            *pexit_code = 255;
            return code;
        case gs_error_Quit:
            *perror_object = osp[-1];
            *pexit_code = code = osp->value.intval;
            osp -= 2;
            return
                (code == 0 ? gs_error_Quit :
                 code < 0 && code > -100 ? code : gs_error_Fatal);
        case gs_error_InterpreterExit:
            return 0;
        case gs_error_ExecStackUnderflow:
/****** WRONG -- must keep mark blocks intact ******/
            ref_stack_pop_block(&e_stack);
            doref = *perror_object;
            epref = &doref;
            goto again;
        case gs_error_VMreclaim:
            /* Do the GC and continue. */
            /* We ignore the return value here, if it fails here
             * we'll call it again having jumped to the "again" label.
             * Where, assuming it fails again, we'll handle the error.
             */
            (void)interp_reclaim(pi_ctx_p,
                                  (osp->value.intval == 2 ?
                                   avm_global : avm_local));
            i_ctx_p = *pi_ctx_p;
            make_oper(&doref, 0, zpop);
            epref = &doref;
            goto again;
        case gs_error_NeedInput:
        case gs_error_interrupt:
            return code;
    }
    /* Adjust osp in case of operand stack underflow */
    if (osp < osbot - 1)
        osp = osbot - 1;
    /* We have to handle stack over/underflow specially, because */
    /* we might be able to recover by adding or removing a block. */
    switch (code) {
        case gs_error_dictstackoverflow:
            /* We don't have to handle this specially: */
            /* The only places that could generate it */
            /* use check_dstack, which does a ref_stack_extend, */
            /* so if` we get this error, it's a real one. */
            if (osp >= ostop) {
                if ((ccode = ref_stack_extend(&o_stack, 1)) < 0)
                    return ccode;
            }
            /* Skip system dictionaries for CET 20-02-02 */
            ccode = copy_stack(i_ctx_p, &d_stack, min_dstack_size, &saref);
            if (ccode < 0)
                return ccode;
            ref_stack_pop_to(&d_stack, min_dstack_size);
            dict_set_top();
            *++osp = saref;
            break;
        case gs_error_dictstackunderflow:
            if (ref_stack_pop_block(&d_stack) >= 0) {
                dict_set_top();
                doref = *perror_object;
                epref = &doref;
                goto again;
            }
            break;
        case gs_error_execstackoverflow:
            /* We don't have to handle this specially: */
            /* The only places that could generate it */
            /* use check_estack, which does a ref_stack_extend, */
            /* so if we get this error, it's a real one. */
            if (osp >= ostop) {
                if ((ccode = ref_stack_extend(&o_stack, 1)) < 0)
                    return ccode;
            }
            ccode = copy_stack(i_ctx_p, &e_stack, 0, &saref);
            if (ccode < 0)
                return ccode;
            {
                uint count = ref_stack_count(&e_stack);
                uint limit = ref_stack_max_count(&e_stack) - ES_HEADROOM;

                if (count > limit) {
                    /*
                     * If there is an e-stack mark within MIN_BLOCK_ESTACK of
                     * the new top, cut the stack back to remove the mark.
                     */
                    int skip = count - limit;
                    int i;

                    for (i = skip; i < skip + MIN_BLOCK_ESTACK; ++i) {
                        const ref *ep = ref_stack_index(&e_stack, i);

                        if (r_has_type_attrs(ep, t_null, a_executable)) {
                            skip = i + 1;
                            break;
                        }
                    }
                    pop_estack(i_ctx_p, skip);
                }
            }
            *++osp = saref;
            break;
        case gs_error_stackoverflow:
            if (ref_stack_extend(&o_stack, o_stack.requested) >= 0) {   /* We can't just re-execute the object, because */
                /* it might be a procedure being pushed as a */
                /* literal.  We check for this case specially. */
                doref = *perror_object;
                if (r_is_proc(&doref)) {
                    *++osp = doref;
                    make_null_proc(&doref);
                }
                epref = &doref;
                goto again;
            }
            ccode = copy_stack(i_ctx_p, &o_stack, 0, &saref);
            if (ccode < 0)
                return ccode;
            ref_stack_clear(&o_stack);
            *++osp = saref;
            break;
        case gs_error_stackunderflow:
            if (ref_stack_pop_block(&o_stack) >= 0) {
                doref = *perror_object;
                epref = &doref;
                goto again;
            }
            break;
    }
    if (user_errors < 0)
        return code;
    if (gs_errorname(i_ctx_p, code, &error_name) < 0)
        return code;            /* out-of-range error code! */
    /*
     * For greater Adobe compatibility, only the standard PostScript errors
     * are defined in errordict; the rest are in gserrordict.
     */
    if (dict_find_string(systemdict, "errordict", &perrordict) <= 0 ||
        (dict_find(perrordict, &error_name, &epref) <= 0 &&
         (dict_find_string(systemdict, "gserrordict", &perrordict) <= 0 ||
          dict_find(perrordict, &error_name, &epref) <= 0))
        )
        return code;            /* error name not in errordict??? */
    doref = *epref;
    epref = &doref;
     /* Push the error object on the operand stack if appropriate. */
     if (!GS_ERROR_IS_INTERRUPT(code)) {
         /* Replace the error object if within an oparray or .errorexec. */
        osp++;
        if (osp >= ostop) {
            *pexit_code = gs_error_Fatal;
            return_error(gs_error_Fatal);
        }
        *osp = *perror_object;
         errorexec_find(i_ctx_p, osp);
     }
     goto again;
}


gs_main_finit(gs_main_instance * minst, int exit_status, int code)
{
    i_ctx_t *i_ctx_p = minst->i_ctx_p;
    gs_dual_memory_t dmem = {0};
    int exit_code;
    ref error_object;
    char *tempnames;

    /* NB: need to free gs_name_table
     */

    /*
     * Previous versions of this code closed the devices in the
     * device list here.  Since these devices are now prototypes,
     * they cannot be opened, so they do not need to be closed;
     * alloc_restore_all will close dynamically allocated devices.
     */
    tempnames = gs_main_tempnames(minst);

    /* by the time we get here, we *must* avoid any random redefinitions of
     * operators etc, so we push systemdict onto the top of the dict stack.
     * We do this in C to avoid running into any other re-defininitions in the
     * Postscript world.
     */
    gs_finit_push_systemdict(i_ctx_p);

    /* We have to disable BGPrint before we call interp_reclaim() to prevent the
     * parent rendering thread initialising for the next page, whilst we are
     * removing objects it may want to access - for example, the I/O device table.
     * We also have to mess with the BeginPage/EndPage procs so that we don't
     * trigger a spurious extra page to be emitted.
     */
    if (minst->init_done >= 2) {
        gs_main_run_string(minst,
            "/BGPrint /GetDeviceParam .special_op \
            {{ <</BeginPage {pop} /EndPage {pop pop //false } \
              /BGPrint false /NumRenderingThreads 0>> setpagedevice} if} if \
              serverdict /.jobsavelevel get 0 eq {/quit} {/stop} ifelse \
              .systemvar exec",
            0 , &exit_code, &error_object);
    }

    /*
     * Close the "main" device, because it may need to write out
     * data before destruction. pdfwrite needs so.
     */
    if (minst->init_done >= 2) {
        int code = 0;

        if (idmemory->reclaim != 0) {
            code = interp_reclaim(&minst->i_ctx_p, avm_global);

            if (code < 0) {
                ref error_name;
                if (tempnames)
                    free(tempnames);

                if (gs_errorname(i_ctx_p, code, &error_name) >= 0) {
                    char err_str[32] = {0};
                    name_string_ref(imemory, &error_name, &error_name);
                    memcpy(err_str, error_name.value.const_bytes, r_size(&error_name));
                    emprintf2(imemory, "ERROR: %s (%d) reclaiming the memory while the interpreter finalization.\n", err_str, code);
                }
                else {
                    emprintf1(imemory, "UNKNOWN ERROR %d reclaiming the memory while the interpreter finalization.\n", code);
                }
#ifdef MEMENTO_SQUEEZE_BUILD
                if (code != gs_error_VMerror ) return gs_error_Fatal;
#else
                return gs_error_Fatal;
#endif
            }
             i_ctx_p = minst->i_ctx_p; /* interp_reclaim could change it. */
         }

         if (i_ctx_p->pgs != NULL && i_ctx_p->pgs->device != NULL) {
             gx_device *pdev = i_ctx_p->pgs->device;
             const char * dname = pdev->dname;
            if (code < 0) {
                ref error_name;
                if (gs_errorname(i_ctx_p, code, &error_name) >= 0) {
                    char err_str[32] = {0};
                    name_string_ref(imemory, &error_name, &error_name);
                    memcpy(err_str, error_name.value.const_bytes, r_size(&error_name));
                    emprintf3(imemory, "ERROR: %s (%d) on closing %s device.\n", err_str, code, dname);
                }
                else {
                    emprintf2(imemory, "UNKNOWN ERROR %d closing %s device.\n", code, dname);
               }
            }
            rc_decrement(pdev, "gs_main_finit");                /* device might be freed */
            if (exit_status == 0 || exit_status == gs_error_Quit)
                exit_status = code;
        }

      /* Flush stdout and stderr */
      gs_main_run_string(minst,
        "(%stdout) (w) file closefile (%stderr) (w) file closefile \
        serverdict /.jobsavelevel get 0 eq {/quit} {/stop} ifelse .systemexec \
          systemdict /savedinitialgstate .forceundef",
        0 , &exit_code, &error_object);
    }
    gp_readline_finit(minst->readline_data);
    i_ctx_p = minst->i_ctx_p;		/* get current interp context */
    if (gs_debug_c(':')) {
        print_resource_usage(minst, &gs_imemory, "Final");
        dmprintf1(minst->heap, "%% Exiting instance 0x%p\n", minst);
    }
    /* Do the equivalent of a restore "past the bottom". */
    /* This will release all memory, close all open files, etc. */
    if (minst->init_done >= 1) {
        gs_memory_t *mem_raw = i_ctx_p->memory.current->non_gc_memory;
        i_plugin_holder *h = i_ctx_p->plugin_list;

        dmem = *idmemory;
        code = alloc_restore_all(i_ctx_p);
        if (code < 0)
            emprintf1(mem_raw,
                      "ERROR %d while the final restore. See gs/psi/ierrors.h for code explanation.\n",
                      code);
        i_iodev_finit(&dmem);
        i_plugin_finit(mem_raw, h);
    }

    /* clean up redirected stdout */
    if (minst->heap->gs_lib_ctx->fstdout2
        && (minst->heap->gs_lib_ctx->fstdout2 != minst->heap->gs_lib_ctx->fstdout)
        && (minst->heap->gs_lib_ctx->fstdout2 != minst->heap->gs_lib_ctx->fstderr)) {
        fclose(minst->heap->gs_lib_ctx->fstdout2);
        minst->heap->gs_lib_ctx->fstdout2 = (FILE *)NULL;
    }

    minst->heap->gs_lib_ctx->stdout_is_redirected = 0;
    minst->heap->gs_lib_ctx->stdout_to_stderr = 0;
    /* remove any temporary files, after ghostscript has closed files */
    if (tempnames) {
        char *p = tempnames;
        while (*p) {
            unlink(p);
            p += strlen(p) + 1;
        }
        free(tempnames);
    }
    gs_lib_finit(exit_status, code, minst->heap);

    gs_free_object(minst->heap, minst->lib_path.container.value.refs, "lib_path array");
    ialloc_finit(&dmem);
    return exit_status;
}

In Artifex Ghostscript before 9.24, attackers able to supply crafted PostScript files could use incorrect free logic in pagedevice replacement to crash the interpreter.


gs_main_finit(gs_main_instance * minst, int exit_status, int code)
{
    i_ctx_t *i_ctx_p = minst->i_ctx_p;
    gs_dual_memory_t dmem = {0};
    int exit_code;
    ref error_object;
    char *tempnames;

    /* NB: need to free gs_name_table
     */

    /*
     * Previous versions of this code closed the devices in the
     * device list here.  Since these devices are now prototypes,
     * they cannot be opened, so they do not need to be closed;
     * alloc_restore_all will close dynamically allocated devices.
     */
    tempnames = gs_main_tempnames(minst);

    /* by the time we get here, we *must* avoid any random redefinitions of
     * operators etc, so we push systemdict onto the top of the dict stack.
     * We do this in C to avoid running into any other re-defininitions in the
     * Postscript world.
     */
    gs_finit_push_systemdict(i_ctx_p);

    /* We have to disable BGPrint before we call interp_reclaim() to prevent the
     * parent rendering thread initialising for the next page, whilst we are
     * removing objects it may want to access - for example, the I/O device table.
     * We also have to mess with the BeginPage/EndPage procs so that we don't
     * trigger a spurious extra page to be emitted.
     */
    if (minst->init_done >= 2) {
        gs_main_run_string(minst,
            "/BGPrint /GetDeviceParam .special_op \
            {{ <</BeginPage {pop} /EndPage {pop pop //false } \
              /BGPrint false /NumRenderingThreads 0>> setpagedevice} if} if \
              serverdict /.jobsavelevel get 0 eq {/quit} {/stop} ifelse \
              .systemvar exec",
            0 , &exit_code, &error_object);
    }

    /*
     * Close the "main" device, because it may need to write out
     * data before destruction. pdfwrite needs so.
     */
    if (minst->init_done >= 2) {
        int code = 0;

        if (idmemory->reclaim != 0) {
            code = interp_reclaim(&minst->i_ctx_p, avm_global);

            if (code < 0) {
                ref error_name;
                if (tempnames)
                    free(tempnames);

                if (gs_errorname(i_ctx_p, code, &error_name) >= 0) {
                    char err_str[32] = {0};
                    name_string_ref(imemory, &error_name, &error_name);
                    memcpy(err_str, error_name.value.const_bytes, r_size(&error_name));
                    emprintf2(imemory, "ERROR: %s (%d) reclaiming the memory while the interpreter finalization.\n", err_str, code);
                }
                else {
                    emprintf1(imemory, "UNKNOWN ERROR %d reclaiming the memory while the interpreter finalization.\n", code);
                }
#ifdef MEMENTO_SQUEEZE_BUILD
                if (code != gs_error_VMerror ) return gs_error_Fatal;
#else
                return gs_error_Fatal;
#endif
            }
             i_ctx_p = minst->i_ctx_p; /* interp_reclaim could change it. */
         }

        if (i_ctx_p->pgs != NULL && i_ctx_p->pgs->device != NULL &&
            gx_device_is_null(i_ctx_p->pgs->device)) {
            /* if the job replaced the device with the nulldevice, we we need to grestore
               away that device, so the block below can properly dispense
               with the default device.
             */
            int code = gs_grestoreall(i_ctx_p->pgs);
            if (code < 0) return_error(gs_error_Fatal);
        }

         if (i_ctx_p->pgs != NULL && i_ctx_p->pgs->device != NULL) {
             gx_device *pdev = i_ctx_p->pgs->device;
             const char * dname = pdev->dname;
            if (code < 0) {
                ref error_name;
                if (gs_errorname(i_ctx_p, code, &error_name) >= 0) {
                    char err_str[32] = {0};
                    name_string_ref(imemory, &error_name, &error_name);
                    memcpy(err_str, error_name.value.const_bytes, r_size(&error_name));
                    emprintf3(imemory, "ERROR: %s (%d) on closing %s device.\n", err_str, code, dname);
                }
                else {
                    emprintf2(imemory, "UNKNOWN ERROR %d closing %s device.\n", code, dname);
               }
            }
            rc_decrement(pdev, "gs_main_finit");                /* device might be freed */
            if (exit_status == 0 || exit_status == gs_error_Quit)
                exit_status = code;
        }

      /* Flush stdout and stderr */
      gs_main_run_string(minst,
        "(%stdout) (w) file closefile (%stderr) (w) file closefile \
        serverdict /.jobsavelevel get 0 eq {/quit} {/stop} ifelse .systemexec \
          systemdict /savedinitialgstate .forceundef",
        0 , &exit_code, &error_object);
    }
    gp_readline_finit(minst->readline_data);
    i_ctx_p = minst->i_ctx_p;		/* get current interp context */
    if (gs_debug_c(':')) {
        print_resource_usage(minst, &gs_imemory, "Final");
        dmprintf1(minst->heap, "%% Exiting instance 0x%p\n", minst);
    }
    /* Do the equivalent of a restore "past the bottom". */
    /* This will release all memory, close all open files, etc. */
    if (minst->init_done >= 1) {
        gs_memory_t *mem_raw = i_ctx_p->memory.current->non_gc_memory;
        i_plugin_holder *h = i_ctx_p->plugin_list;

        dmem = *idmemory;
        code = alloc_restore_all(i_ctx_p);
        if (code < 0)
            emprintf1(mem_raw,
                      "ERROR %d while the final restore. See gs/psi/ierrors.h for code explanation.\n",
                      code);
        i_iodev_finit(&dmem);
        i_plugin_finit(mem_raw, h);
    }

    /* clean up redirected stdout */
    if (minst->heap->gs_lib_ctx->fstdout2
        && (minst->heap->gs_lib_ctx->fstdout2 != minst->heap->gs_lib_ctx->fstdout)
        && (minst->heap->gs_lib_ctx->fstdout2 != minst->heap->gs_lib_ctx->fstderr)) {
        fclose(minst->heap->gs_lib_ctx->fstdout2);
        minst->heap->gs_lib_ctx->fstdout2 = (FILE *)NULL;
    }

    minst->heap->gs_lib_ctx->stdout_is_redirected = 0;
    minst->heap->gs_lib_ctx->stdout_to_stderr = 0;
    /* remove any temporary files, after ghostscript has closed files */
    if (tempnames) {
        char *p = tempnames;
        while (*p) {
            unlink(p);
            p += strlen(p) + 1;
        }
        free(tempnames);
    }
    gs_lib_finit(exit_status, code, minst->heap);

    gs_free_object(minst->heap, minst->lib_path.container.value.refs, "lib_path array");
    ialloc_finit(&dmem);
    return exit_status;
}


zsetcolor(i_ctx_t * i_ctx_p)
{
    os_ptr                  op = osp;
    es_ptr ep;
    const gs_color_space *  pcs = gs_currentcolorspace(igs);
    gs_client_color         cc;
    int                     n_comps, n_numeric_comps, num_offset = 0, code, depth;
    PS_colour_space_t *space;

    /* initialize the client color pattern pointer for GC */
    cc.pattern = 0;

    /* check for a pattern color space */
    if ((n_comps = cs_num_components(pcs)) < 0) {
        n_comps = -n_comps;
         if (r_has_type(op, t_dictionary)) {
             ref     *pImpl, pPatInst;

            code = dict_find_string(op, "Implementation", &pImpl);
            if (code != 0) {
                 code = array_get(imemory, pImpl, 0, &pPatInst);
                 if (code < 0)
                     return code;
                n_numeric_comps = ( pattern_instance_uses_base_space(cc.pattern)
                      ? n_comps - 1
                      : 0 );
            } else
                n_numeric_comps = 0;
        } else
            n_numeric_comps = 0;
        num_offset = 1;
    } else
        n_numeric_comps = n_comps;

    /* gather the numeric operands */
    code = float_params(op - num_offset, n_numeric_comps, cc.paint.values);
    if (code < 0)
        return code;
    /* The values are copied to graphic state and compared with */
    /* other colors by memcmp() in gx_hld_saved_color_equal()   */
    /* This is the easiest way to avoid indeterminism */
    memset(cc.paint.values + n_numeric_comps, 0,
            sizeof(cc.paint.values) - sizeof(*cc.paint.values)*n_numeric_comps);

    code = get_space_object(i_ctx_p, &istate->colorspace[0].array, &space);
    if (code < 0)
        return code;
    if (space->validatecomponents) {
        code = space->validatecomponents(i_ctx_p,
                                         &istate->colorspace[0].array,
                                         cc.paint.values, n_numeric_comps);
        if (code < 0)
            return code;
    }

    /* pass the color to the graphic library */
    if ((code = gs_setcolor(igs, &cc)) >= 0) {

        if (n_comps > n_numeric_comps) {
            istate->pattern[0] = *op;      /* save pattern dict or null */
        }
    }

    /* Check the color spaces, to see if we need to run any tint transform
     * procedures. Some Adobe applications *eg Photoshop) expect that the
     * tint transform will be run and use this to set up duotone DeviceN
     * spaces.
     */
    code = validate_spaces(i_ctx_p, &istate->colorspace[0].array, &depth);
    if (code < 0)
        return code;
    /* Set up for the continuation procedure which will do the work */
    /* Make sure the exec stack has enough space */
    check_estack(5);
    /* A place holder for data potentially used by transform functions */
    ep = esp += 1;
    make_int(ep, 0);
    /* Store the 'depth' of the space returned during checking above */
    ep = esp += 1;
    make_int(ep, 0);
    /* Store the 'stage' of processing (initially 0) */
    ep = esp += 1;
    make_int(ep, 0);
    /* Store a pointer to the color space stored on the operand stack
     * as the stack may grow unpredictably making further access
     * to the space difficult
     */
    ep = esp += 1;
    *ep = istate->colorspace[0].array;
    /* Finally, the actual continuation routine */
    push_op_estack(setcolor_cont);
    return o_push_estack;
}

In Artifex Ghostscript before 9.24, attackers able to supply crafted PostScript files could use a type confusion in the setcolor function to crash the interpreter or possibly have unspecified other impact.


zsetcolor(i_ctx_t * i_ctx_p)
{
    os_ptr                  op = osp;
    es_ptr ep;
    const gs_color_space *  pcs = gs_currentcolorspace(igs);
    gs_client_color         cc;
    int                     n_comps, n_numeric_comps, num_offset = 0, code, depth;
    PS_colour_space_t *space;

    /* initialize the client color pattern pointer for GC */
    cc.pattern = 0;

    /* check for a pattern color space */
    if ((n_comps = cs_num_components(pcs)) < 0) {
        n_comps = -n_comps;
         if (r_has_type(op, t_dictionary)) {
             ref     *pImpl, pPatInst;

            if ((code = dict_find_string(op, "Implementation", &pImpl)) < 0)
                return code;
            if (code > 0) {
                 code = array_get(imemory, pImpl, 0, &pPatInst);
                 if (code < 0)
                     return code;
                n_numeric_comps = ( pattern_instance_uses_base_space(cc.pattern)
                      ? n_comps - 1
                      : 0 );
            } else
                n_numeric_comps = 0;
        } else
            n_numeric_comps = 0;
        num_offset = 1;
    } else
        n_numeric_comps = n_comps;

    /* gather the numeric operands */
    code = float_params(op - num_offset, n_numeric_comps, cc.paint.values);
    if (code < 0)
        return code;
    /* The values are copied to graphic state and compared with */
    /* other colors by memcmp() in gx_hld_saved_color_equal()   */
    /* This is the easiest way to avoid indeterminism */
    memset(cc.paint.values + n_numeric_comps, 0,
            sizeof(cc.paint.values) - sizeof(*cc.paint.values)*n_numeric_comps);

    code = get_space_object(i_ctx_p, &istate->colorspace[0].array, &space);
    if (code < 0)
        return code;
    if (space->validatecomponents) {
        code = space->validatecomponents(i_ctx_p,
                                         &istate->colorspace[0].array,
                                         cc.paint.values, n_numeric_comps);
        if (code < 0)
            return code;
    }

    /* pass the color to the graphic library */
    if ((code = gs_setcolor(igs, &cc)) >= 0) {

        if (n_comps > n_numeric_comps) {
            istate->pattern[0] = *op;      /* save pattern dict or null */
        }
    }

    /* Check the color spaces, to see if we need to run any tint transform
     * procedures. Some Adobe applications *eg Photoshop) expect that the
     * tint transform will be run and use this to set up duotone DeviceN
     * spaces.
     */
    code = validate_spaces(i_ctx_p, &istate->colorspace[0].array, &depth);
    if (code < 0)
        return code;
    /* Set up for the continuation procedure which will do the work */
    /* Make sure the exec stack has enough space */
    check_estack(5);
    /* A place holder for data potentially used by transform functions */
    ep = esp += 1;
    make_int(ep, 0);
    /* Store the 'depth' of the space returned during checking above */
    ep = esp += 1;
    make_int(ep, 0);
    /* Store the 'stage' of processing (initially 0) */
    ep = esp += 1;
    make_int(ep, 0);
    /* Store a pointer to the color space stored on the operand stack
     * as the stack may grow unpredictably making further access
     * to the space difficult
     */
    ep = esp += 1;
    *ep = istate->colorspace[0].array;
    /* Finally, the actual continuation routine */
    push_op_estack(setcolor_cont);
    return o_push_estack;
}


fd_read_body (const char *downloaded_filename, int fd, FILE *out, wgint toread, wgint startpos,

              wgint *qtyread, wgint *qtywritten, double *elapsed, int flags,
              FILE *out2)
{
  int ret = 0;
#undef max
#define max(a,b) ((a) > (b) ? (a) : (b))
  int dlbufsize = max (BUFSIZ, 8 * 1024);
  char *dlbuf = xmalloc (dlbufsize);

  struct ptimer *timer = NULL;
  double last_successful_read_tm = 0;

  /* The progress gauge, set according to the user preferences. */
  void *progress = NULL;

  /* Non-zero if the progress gauge is interactive, i.e. if it can
     continually update the display.  When true, smaller timeout
     values are used so that the gauge can update the display when
     data arrives slowly. */
  bool progress_interactive = false;

  bool exact = !!(flags & rb_read_exactly);

  /* Used only by HTTP/HTTPS chunked transfer encoding.  */
  bool chunked = flags & rb_chunked_transfer_encoding;
  wgint skip = 0;

  /* How much data we've read/written.  */
  wgint sum_read = 0;
  wgint sum_written = 0;
  wgint remaining_chunk_size = 0;

#ifdef HAVE_LIBZ
  /* try to minimize the number of calls to inflate() and write_data() per
     call to fd_read() */
  unsigned int gzbufsize = dlbufsize * 4;
  char *gzbuf = NULL;
  z_stream gzstream;

  if (flags & rb_compressed_gzip)
    {
      gzbuf = xmalloc (gzbufsize);
      if (gzbuf != NULL)
        {
          gzstream.zalloc = zalloc;
          gzstream.zfree = zfree;
          gzstream.opaque = Z_NULL;
          gzstream.next_in = Z_NULL;
          gzstream.avail_in = 0;

          #define GZIP_DETECT 32 /* gzip format detection */
          #define GZIP_WINDOW 15 /* logarithmic window size (default: 15) */
          ret = inflateInit2 (&gzstream, GZIP_DETECT | GZIP_WINDOW);
          if (ret != Z_OK)
            {
              xfree (gzbuf);
              errno = (ret == Z_MEM_ERROR) ? ENOMEM : EINVAL;
              ret = -1;
              goto out;
            }
        }
      else
        {
          errno = ENOMEM;
          ret = -1;
          goto out;
        }
    }
#endif

  if (flags & rb_skip_startpos)
    skip = startpos;

  if (opt.show_progress)
    {
      const char *filename_progress;
      /* If we're skipping STARTPOS bytes, pass 0 as the INITIAL
         argument to progress_create because the indicator doesn't
         (yet) know about "skipping" data.  */
      wgint start = skip ? 0 : startpos;
      if (opt.dir_prefix)
        filename_progress = downloaded_filename + strlen (opt.dir_prefix) + 1;
      else
        filename_progress = downloaded_filename;
      progress = progress_create (filename_progress, start, start + toread);
      progress_interactive = progress_interactive_p (progress);
    }

  if (opt.limit_rate)
    limit_bandwidth_reset ();

  /* A timer is needed for tracking progress, for throttling, and for
     tracking elapsed time.  If either of these are requested, start
     the timer.  */
  if (progress || opt.limit_rate || elapsed)
    {
      timer = ptimer_new ();
      last_successful_read_tm = 0;
    }

  /* Use a smaller buffer for low requested bandwidths.  For example,
     with --limit-rate=2k, it doesn't make sense to slurp in 16K of
     data and then sleep for 8s.  With buffer size equal to the limit,
     we never have to sleep for more than one second.  */
  if (opt.limit_rate && opt.limit_rate < dlbufsize)
    dlbufsize = opt.limit_rate;

  /* Read from FD while there is data to read.  Normally toread==0
     means that it is unknown how much data is to arrive.  However, if
     EXACT is set, then toread==0 means what it says: that no data
     should be read.  */
  while (!exact || (sum_read < toread))
    {
      int rdsize;
      double tmout = opt.read_timeout;

      if (chunked)
        {
          if (remaining_chunk_size == 0)
            {
              char *line = fd_read_line (fd);
              char *endl;
              if (line == NULL)
                {
                  ret = -1;
                  break;
                }
              else if (out2 != NULL)
                fwrite (line, 1, strlen (line), out2);

               remaining_chunk_size = strtol (line, &endl, 16);
               xfree (line);

               if (remaining_chunk_size == 0)
                 {
                   ret = 0;
                        fwrite (line, 1, strlen (line), out2);
                      xfree (line);
                    }
                  break;
                }
            }

          rdsize = MIN (remaining_chunk_size, dlbufsize);
        }
      else
        rdsize = exact ? MIN (toread - sum_read, dlbufsize) : dlbufsize;

      if (progress_interactive)
        {
          /* For interactive progress gauges, always specify a ~1s
             timeout, so that the gauge can be updated regularly even
             when the data arrives very slowly or stalls.  */
          tmout = 0.95;
          if (opt.read_timeout)
            {
              double waittm;
              waittm = ptimer_read (timer) - last_successful_read_tm;
              if (waittm + tmout > opt.read_timeout)
                {
                  /* Don't let total idle time exceed read timeout. */
                  tmout = opt.read_timeout - waittm;
                  if (tmout < 0)
                    {
                      /* We've already exceeded the timeout. */
                      ret = -1, errno = ETIMEDOUT;
                      break;
                    }
                }
            }
        }
      ret = fd_read (fd, dlbuf, rdsize, tmout);

      if (progress_interactive && ret < 0 && errno == ETIMEDOUT)
        ret = 0;                /* interactive timeout, handled above */
      else if (ret <= 0)
        break;                  /* EOF or read error */

      if (progress || opt.limit_rate || elapsed)
        {
          ptimer_measure (timer);
          if (ret > 0)
            last_successful_read_tm = ptimer_read (timer);
        }

      if (ret > 0)
        {
          int write_res;

          sum_read += ret;

#ifdef HAVE_LIBZ
          if (gzbuf != NULL)
            {
              int err;
              int towrite;
              gzstream.avail_in = ret;
              gzstream.next_in = (unsigned char *) dlbuf;

              do
                {
                  gzstream.avail_out = gzbufsize;
                  gzstream.next_out = (unsigned char *) gzbuf;

                  err = inflate (&gzstream, Z_NO_FLUSH);

                  switch (err)
                    {
                    case Z_MEM_ERROR:
                      errno = ENOMEM;
                      ret = -1;
                      goto out;
                    case Z_NEED_DICT:
                    case Z_DATA_ERROR:
                      errno = EINVAL;
                      ret = -1;
                      goto out;
                    case Z_STREAM_END:
                      if (exact && sum_read != toread)
                        {
                          DEBUGP(("zlib stream ended unexpectedly after "
                                  "%ld/%ld bytes\n", sum_read, toread));
                        }
                    }

                  towrite = gzbufsize - gzstream.avail_out;
                  write_res = write_data (out, out2, gzbuf, towrite, &skip,
                                          &sum_written);
                  if (write_res < 0)
                    {
                      ret = (write_res == -3) ? -3 : -2;
                      goto out;
                    }
                }
              while (gzstream.avail_out == 0);
            }
          else
#endif
            {
              write_res = write_data (out, out2, dlbuf, ret, &skip,
                                      &sum_written);
              if (write_res < 0)
                {
                  ret = (write_res == -3) ? -3 : -2;
                  goto out;
                }
            }

          if (chunked)
            {
              remaining_chunk_size -= ret;
              if (remaining_chunk_size == 0)
                {
                  char *line = fd_read_line (fd);
                  if (line == NULL)
                    {
                      ret = -1;
                      break;
                    }
                  else
                    {
                      if (out2 != NULL)
                        fwrite (line, 1, strlen (line), out2);
                      xfree (line);
                    }
                }
            }
        }

      if (opt.limit_rate)
        limit_bandwidth (ret, timer);

      if (progress)
        progress_update (progress, ret, ptimer_read (timer));
#ifdef WINDOWS
      if (toread > 0 && opt.show_progress)
        ws_percenttitle (100.0 *
                         (startpos + sum_read) / (startpos + toread));
#endif
    }

The retr.c:fd_read_body() function is called when processing OK responses. When the response is sent chunked in wget before 1.19.2, the chunk parser uses strtol() to read each chunk's length, but doesn't check that the chunk length is a non-negative number. The code then tries to read the chunk in pieces of 8192 bytes by using the MIN() macro, but ends up passing the negative chunk length to retr.c:fd_read(). As fd_read() takes an int argument, the high 32 bits of the chunk length are discarded, leaving fd_read() with a completely attacker controlled length argument. The attacker can corrupt malloc metadata after the allocated buffer.