From: Werner Fink <werner@suse.de>
Date: Fri, 26 Jul 2019 12:54:12 +0200
Subject: Workaround various attack vectors in ghostscript of regaining permissions

First note: This patch does NOT port the real upstream fixes done for ghostscript 9.27
below its License AGPLv3 (GNU Affero General Public License) back to ghostscript 8.62
below GPLv2 (GNU General Public License) as such ports can not be distributed with
the GPLv2 anymore.

This patch is a workaround which uses the libc API functions opendir(3), open64(2),
open(2), fopen64(3), fopen(3), and popen(3) to enwrap those API functions with
enhanced checks.  Those checks include file locations to allow or forbid reading
and/or writting those files.  To make this work with the ghostscript option -dSAFER
the program options in the array of argument strings passed to e.g. ghostscript
in the API libgs function gs_main_init_with_args() and also after the main
initialing PostScript[tm] files are executed by the ghostscript command interpreter
within the API libgs function gs_run_init_file().

This allows that the interfaces %pipe% as well as the "deletefile" and "renamefile"
operators can not be used or modified with the ghostscript option -dSAFER.

The read permission for files are only given below the official localizations

       /etc/fonts
       /etc/ghostscript
       /usr/lib/ghostscript
       /usr/lib64/ghostscript
       /usr/share/ghostscript
       /usr/share/fonts
       /var/cache/fontconfig

as well as in the paths specified in the environment variables

       GS_LIB_DEFAULT
       GS_LIB
       GS_FONTPATH

which implies that below those specified paths no confidential data should
be stored.  Beside this there are confidential sub directories below the users
$HOME directory:

      .ssh
      .gnupg

which are not accessible anymore.  On need the system adminstrator can specify
more sub directories without any leading $HOME prefix in the configuration file

      /etc/ghostscript/forbidden

One last step further is that ghostscipt can only write to the following special
device files

      /dev/null
      /dev/zero
      /dev/lp*      where * means /dev/lp0, /dev/lp1, ...
      /dev/usb/lp*  where * means /dev/usb/lp0, /dev/usb/lp1, ...

In should be noted, that all paths for the file locations are expanded by the
libc API function realpath(3) therefore even symbolic links are expanded to
the real location.  This means, e.g. that links below the official localizations
can not read.

---
 src/dirent_.h   |    6 
 src/fcntl_.h    |    5 
 src/gsdevice.c  |    4 
 src/gswrap.c    |  675 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/imain.c     |    6 
 src/imainarg.c  |    3 
 src/lib.mak     |    7 
 src/stdio_.h    |   16 +
 src/zfile.c     |   13 -
 9 files changed, 731 insertions(+), 3 deletions(-)

--- src/dirent_.h
+++ src/dirent_.h	2019-07-26 07:35:35.245200421 +0000
@@ -49,4 +49,10 @@ typedef struct direct dir_entry;
 
 #endif /* sys/ndir or ndir or sys/dir */
 
+extern DIR  *_gswrap_opendir(const char *);
+extern inline __attribute__ ((__gnu_inline__)) DIR *opendir(const char *name)
+{
+    return _gswrap_opendir(name);
+}
+
 #endif /* dirent__INCLUDED */
--- src/fcntl_.h
+++ src/fcntl_.h	2019-07-26 07:35:35.245200421 +0000
@@ -53,4 +53,9 @@
 #  define O_WRONLY _O_WRONLY
 #endif
 
+extern int _gswrap_open64(const char *, int, ...);
+extern int _gswrap_open(const char *, int, ...);
+#define open64(pathname, args...) _gswrap_open64(pathname, args);
+#define open(pathname, args...)   _gswrap_open(pathname, args);
+
 #endif /* fcntl__INCLUDED */
--- src/gsdevice.c
+++ src/gsdevice.c	2019-07-26 07:35:35.245200421 +0000
@@ -884,6 +884,8 @@ gx_parse_output_file_name(gs_parsed_file
     return 0;
 }
 
+extern void add_outputfile(const char *fname);
+
 /* Open the output file for a device. */
 int
 gx_device_open_output_file(const gx_device * dev, char *fname,
@@ -930,12 +932,14 @@ gx_device_open_output_file(const gx_devi
 	strcpy(fmode, gp_fmode_wb);
   	if (positionable && strcmp(parsed.iodev->dname, "%pipe%"))
   	    strcat(fmode, "+");
+	add_outputfile(parsed.fname);
  	code = parsed.iodev->procs.fopen(parsed.iodev, parsed.fname, fmode,
   					 pfile, NULL, 0);
  	if (code)
      	    eprintf1("**** Could not open the file %s .\n", parsed.fname);
  	return code;
     }
+    add_outputfile((pfname[0] ? pfname : fname));
     *pfile = gp_open_printer((pfname[0] ? pfname : fname), binary);
     if (*pfile)
   	return 0;
--- src/gswrap.c
+++ src/gswrap.c	2019-07-26 07:29:32.000000000 +0000
@@ -0,0 +1,675 @@
+/*
+ * Copyright (c) 2019 iuSE GmbH Nuernberg, Germany.
+ * Copyright (c) 2019 Werner Fink
+ * 
+ *  Wrapper code for ghostscript.
+ * 
+ *  Please report bugfixes or comments at https://www.suse.com/feedback/
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <unistd.h>
+
+static inline void freep(void *p) { free(*(void**) p); }
+#define cleanup_q __attribute__ ((__cleanup__ (freep)))
+
+typedef struct argv_s {
+    char *outputfile;
+    char *pipecmd;
+    int dosafer;
+    int rwmode;
+    int force;
+} argv_t;
+
+static argv_t options;
+
+typedef struct list_struct {
+    struct list_struct * next, * prev;
+} list_t;
+
+struct files_s {
+    list_t node;
+    char *fname;
+};
+typedef struct files_s outputfiles_t;
+typedef struct files_s inputfiles_t;
+
+typedef struct subdirs_s {
+    list_t node;
+    char *subdir;
+} subdirs_t;
+
+static outputfiles_t *outputfiles;
+static list_t lopfs = { &(lopfs), &(lopfs) };
+
+static inputfiles_t *inputfiles;
+static list_t linfs = { &(linfs), &(linfs) };
+
+static subdirs_t *forbidden;
+static list_t lsdirs = { &(lsdirs), &(lsdirs) };
+
+#ifndef offsetof
+# define offsetof(type,memb)	__builtin_offsetof(type,memb)
+#endif
+
+#define list_entry(ptr, type, member)   (__extension__ ({		\
+	const typeof( ((type *)0)->member ) *__mptr = (ptr);		\
+	((type *)( (char *)(__mptr) - offsetof(type,member) )); }))
+
+#define list_first_entry(ptr, type, member)				\
+	list_entry((ptr)->next, type, member)
+#define list_next_entry(pos, member)					\
+	list_entry((pos)->member.next, typeof(*(pos)), member)
+
+#define list_for_each_entry(pos, head, member)				\
+	for (pos = list_first_entry(head, typeof(*pos), member);	\
+	     &pos->member != (head);					\
+	     pos = list_next_entry(pos, member))
+#define list_for_each_entry_safe(pos, n, head, member)			\
+	for (pos = list_first_entry(head, typeof(*pos), member),	\
+	       n = list_next_entry(pos, member);			\
+	     &pos->member != (head);					\
+	     pos = n, n = list_next_entry(n, member))
+
+static int _gswrap_check(const char *name, int flags)
+{
+    int n, rwflg = O_WRONLY;
+    size_t len;
+    struct stat st;
+    static const char* ropaths[] = {
+	"/etc/fonts",
+	"/etc/ghostscript",
+	"/usr/lib/ghostscript",
+	"/usr/lib64/ghostscript",
+	"/usr/share/ghostscript",
+	"/usr/share/fonts",
+	"/var/cache/fontconfig",
+	NULL
+    };
+    static const char* roenvs[] = {
+	"GS_LIB_DEFAULT",
+	"GS_LIB",
+	"GS_FONTPATH",
+	NULL
+    };
+    /* Take a writable copy of constant name */
+    cleanup_q char *path = NULL;
+    cleanup_q char *cname = strdup(name);
+    char *dir;
+    int oerr;
+
+    if (!cname)
+	goto err;
+
+    /*
+     * Only write only mode is allowed here as for printing
+     * readwrite is not used.
+     */
+    if (!options.rwmode & (flags & O_RDWR)) {
+	errno = EACCES;
+	goto err;
+    }
+
+    oerr = errno;
+    if (stat(cname, &st) < 0) {
+	if (errno != ENOENT)
+	    goto err;
+	if ((flags & O_WRONLY) ==  0)	/* O_RDONLY is zero */
+	    goto err;
+	st.st_mode = S_IFREG;
+    }
+
+    if (flags&O_DIRECTORY) {
+	if ((st.st_mode & S_IFMT) != S_IFDIR) {
+	    errno = ENOTDIR;
+	    goto err;
+	}
+	dir = cname;
+    } else
+	switch (st.st_mode & S_IFMT) {
+	case S_IFCHR:
+	    dir = dirname(cname);
+	    if (strcmp("/dev/null", name) == 0)
+		break;
+	    if (strcmp("/dev/zero", name) == 0)
+		break;
+	    if (strncmp("/dev/lp", name, 7) == 0)
+		break;
+	    if (strncmp("/dev/usb/lp", name, 11) == 0)
+		break;
+	    /* fall through */
+	case S_IFBLK:
+	    errno = EACCES;
+	    goto err;			/* No block and character devices */
+	case S_IFDIR:
+	    errno = EISDIR;
+	    goto err;			/* Not a regular file */
+	case S_IFIFO:			/* pipe */
+	case S_IFSOCK:			/* socketpair e.g. */
+	    goto rw;
+	default:
+	    dir = dirname(cname);
+	    break;
+	}
+    errno = oerr;
+
+    path = realpath(dir, NULL);
+    free(cname);			/* Not used anymore */
+    cname = NULL;			/* Otherwise the cleanup qualifier cause SEGV */
+
+    /*
+     * For CWD dirname(2) returns  a `.' hence
+     * realpath(3) returns the CWD its self ;)
+     */
+    if (!path)
+	goto err;
+
+    if ((flags & (O_WRONLY|O_RDWR)) == 0 && inputfiles) {
+    	inputfiles_t *i;
+	list_for_each_entry(i, &inputfiles->node, node) {
+	    if (strcmp(i->fname, name) == 0)
+		goto ro;
+	}
+    }
+
+    /*
+     * The standard installation of initial postscript
+     * files, fonst and shared objects for ghostscript
+     */
+    for (n = 0; ropaths[n]; n++) {
+	len = strlen(ropaths[n]);
+
+	if (strncmp(ropaths[n], path, len) == 0)
+	    goto ro;
+    }
+
+    /*
+     * The users specified some more via environment variables
+     * fors files, fonst and shared objects for ghostscript.
+     * Hopefully no sets the HOME to e.g. GS_LIB
+     */
+    for (n = 0; roenvs[n]; n++) {
+	const char *env = getenv(roenvs[n]);
+	char *s, *e;
+
+	if (!env || !*env)
+	    continue;
+
+	e = strdupa(env);
+	if (!e)
+	    goto err;
+
+	while ((s = strsep(&e, ":"))) {
+	    if (!*s)
+		continue;
+	    len = strlen(s);
+	    if (strncmp(s, path, len) == 0)
+		goto ro;
+	}
+    }
+
+    /*
+     * Regardless if write, readonly, or readwrite, no access
+     * below the directories in list forbid[]
+     */
+    dir = getenv("HOME");
+    if (dir && *dir) {
+	len = strlen(dir);
+	if (strncmp(dir, path, len) == 0) {
+	    const char *sub = path;
+	    sub += len;
+	    while (*sub == '/')
+		sub++;
+	    if (*sub && forbidden) {
+    		subdirs_t *n;
+		list_for_each_entry(n, &forbidden->node, node) {
+		    len = strlen(n->subdir);
+		    if (strncmp(n->subdir, sub, len) == 0) {
+			errno = EACCES;
+			goto err;
+		    }
+		}
+	    }
+	}
+    }
+
+    /*
+     * Only write only mode is allowed here as for printing readwrite
+     * is beside (eps|ps2|pdf)write mostly not used. Also compare the
+     * files specified by src/gsdevice.c::gx_device_open_output_file()
+     */
+    if (options.rwmode)
+	rwflg |= O_RDWR;
+    if (flags & rwflg) {
+    	outputfiles_t *o;
+	size_t len;
+	char *of;
+
+	if (!options.dosafer)
+	    goto rw;
+    	if (!outputfiles) {
+	    errno = EACCES;
+	    goto err;			/* No output file, no write */
+	}
+	if (!options.outputfile) {
+	    errno = EACCES;		/* No output file on command line!! */
+	    goto err;
+	}
+	of = options.outputfile;
+	len = strlen(of);
+	if (strncmp(of, name, len) != 0) {
+	    errno = EACCES;
+	    goto err;			/* Sanity check */
+	}
+	list_for_each_entry(o, &outputfiles->node, node) {
+	    if (strcmp(o->fname, name) == 0)
+		goto rw;		/* Found official output file */
+    	}
+    }
+    errno = EACCES;
+err:
+    return 0;
+ro:
+    if ((flags & (O_WRONLY|O_RDWR))) {
+	errno = EACCES;
+	return 0;			/* Fonts and library files readonly only */
+    }
+rw:
+    return 1;
+}
+
+extern DIR  *_gswrap_opendir(const char *name);
+DIR  *_gswrap_opendir(const char *name)
+{
+    if (!_gswrap_check(name, O_DIRECTORY))
+	return NULL;
+    return opendir(name);
+}
+
+extern int _gswrap_open64(const char *pathname, int flags, ...);
+int _gswrap_open64(const char *pathname, int flags, ...)
+{
+    if (!_gswrap_check(pathname, flags))
+	return -1;
+    return open64(pathname, flags, __builtin_va_arg_pack());
+}
+
+extern int _gswrap_open(const char *pathname, int flags, ...);
+int _gswrap_open(const char *pathname, int flags, ...)
+{
+    if (!_gswrap_check(pathname, flags))
+	return -1;
+    return open(pathname, flags, __builtin_va_arg_pack());
+}
+
+extern FILE *_gswrap_fopen64(const char *pathname, const char *mode);
+FILE *_gswrap_fopen64(const char *pathname, const char *mode)
+{
+    int flags = 0;
+    const char *c = mode;
+    switch (*c) {
+    case 'r':
+	flags = O_RDONLY;
+	break;
+    case 'w':
+	flags = O_WRONLY|O_CREAT|O_TRUNC;
+	break;
+    case 'a':
+	flags = O_WRONLY|O_CREAT|O_APPEND;
+	break;
+    default:
+	break;
+    }
+    while (*c) {
+	switch (*++c) {
+	case '\0':
+	    break;
+	case 'x':
+	    flags |= O_EXCL;
+	    continue;
+	case 'e':
+	    flags |= O_CLOEXEC;
+	    continue;
+	case '+':
+	    flags |= O_RDWR;
+	    continue;
+	case 'b':
+	default:
+	    continue;
+	}
+	break;
+    }
+    if (!_gswrap_check(pathname, flags))
+	return NULL;
+    return fopen64(pathname, mode);
+}
+
+extern FILE *_gswrap_fopen(const char *pathname, const char *mode);
+FILE *_gswrap_fopen(const char *pathname, const char *mode)
+{
+    int flags = 0;
+    const char *c = mode;
+    switch (*c) {
+    case 'r':
+	flags = O_RDONLY;
+	break;
+    case 'w':
+	flags = O_WRONLY|O_CREAT|O_TRUNC;
+	break;
+    case 'a':
+	flags = O_WRONLY|O_CREAT|O_APPEND;
+	break;
+    default:
+	break;
+    }
+    while (*c) {
+	switch (*++c) {
+	case '\0':
+	    break;
+	case 'x':
+	    flags |= O_EXCL;
+	    continue;
+	case 'e':
+	    flags |= O_CLOEXEC;
+	    continue;
+	case '+':
+	    flags |= O_RDWR;
+	    continue;
+	case 'b':
+	default:
+	    continue;
+	}
+	break;
+    }
+    if (!_gswrap_check(pathname, flags))
+	return NULL;
+    return fopen(pathname, mode);
+}
+
+extern FILE *_gswrap_popen(const char *command, const char *mode);
+FILE *_gswrap_popen(const char *command, const char *mode)
+{
+    FILE *pipe = NULL;
+    if (options.dosafer && options.force) {
+	if (strcmp(options.pipecmd, command) != 0) {
+	    errno = EACCES;
+	    goto err;
+	}
+    }
+    pipe = popen(command, mode);
+err:
+    return pipe;
+}
+
+/*
+ * Catch the "deletefile" and "renamefile" operators as well
+ */
+extern int dosafer(void);
+int dosafer(void)
+{
+    if (options.dosafer && options.force) {
+	errno = EACCES;
+	return -1;
+    }
+    return 0;
+}
+
+#define alignof(type)	((sizeof(type)+(sizeof(void*)-1)) & ~(sizeof(void*)-1))
+
+/*
+ * Insert new entry as next member.
+ */
+static inline void insert (list_t * new, list_t * here)
+{
+    list_t * prev = here;
+    list_t * next = here->next;
+
+    next->prev = new;
+    new->next = next;
+    new->prev = prev;
+    prev->next = new;
+}
+
+static inline void delete (list_t * entry)
+{
+    list_t * prev = entry->prev;
+    list_t * next = entry->next;
+
+    next->prev = prev;
+    prev->next = next;
+}
+
+
+static void clean_outputfile(void)
+{
+    outputfiles_t *m, *n;
+
+    if (!outputfiles)
+	return;
+
+    list_for_each_entry_safe(m, n, &outputfiles->node, node) {
+	delete(&m->node);
+	free(m);
+    }
+    outputfiles = NULL;
+}
+
+extern void add_outputfile(const char *fname);
+void add_outputfile(const char *fname)
+{
+    outputfiles_t *newof;
+    list_t *head;
+
+    if (posix_memalign((void**)&newof, sizeof(void*), alignof(outputfiles_t)+strlen(fname)+1) != 0 || !newof) {
+	fprintf(stderr, "gswrap: can't allocate memory: %m\n");
+	exit(1);
+    }
+    newof->fname = ((char*)newof)+alignof(outputfiles_t);
+    strcpy(newof->fname, fname);
+
+    if (!outputfiles) {
+	head = &lopfs;
+	outputfiles = (outputfiles_t*)head;
+	atexit(clean_outputfile);
+    } else
+	head = &outputfiles->node;
+    insert(&newof->node, head);
+}
+
+static void clean_inputfile(void)
+{
+    inputfiles_t *m, *n;
+
+    if (!inputfiles)
+	return;
+
+    list_for_each_entry_safe(m, n, &inputfiles->node, node) {
+	delete(&m->node);
+	free(m);
+    }
+    inputfiles = NULL;
+}
+
+static void add_inputfile(const char *fname)
+{
+    inputfiles_t *newof;
+    list_t *head;
+
+    if (posix_memalign((void**)&newof, sizeof(void*), alignof(inputfiles_t)+strlen(fname)+1) != 0 || !newof) {
+	fprintf(stderr, "gswrap: can't allocate memory: %m\n");
+	exit(1);
+    }
+    newof->fname = ((char*)newof)+alignof(inputfiles_t);
+    strcpy(newof->fname, fname);
+
+    if (!inputfiles) {
+	head = &linfs;
+	inputfiles = (inputfiles_t*)head;
+	atexit(clean_inputfile);
+    } else
+	head = &inputfiles->node;
+    insert(&newof->node, head);
+}
+
+static void clean_forbidden(void)
+{
+    subdirs_t *m, *n;
+
+    if (!forbidden)
+	return;
+
+    list_for_each_entry_safe(m, n, &forbidden->node, node) {
+	delete(&m->node);
+	free(m);
+    }
+    forbidden = NULL;
+}
+
+static void add_forbidden(const char *subdir)
+{
+    subdirs_t *newsd;
+    list_t *head;
+
+    if (posix_memalign((void**)&newsd, sizeof(void*), alignof(subdirs_t)+strlen(subdir)+1) != 0 || !newsd) {
+	fprintf(stderr, "gswrap: can't allocate memory: %m\n");
+	exit(1);
+    }
+    newsd->subdir = ((char*)newsd)+alignof(subdirs_t);
+    strcpy(newsd->subdir, subdir);
+
+    if (!forbidden) {
+	head = &lsdirs;
+	forbidden = (subdirs_t*)head;
+	atexit(clean_forbidden);
+    } else
+	head = &forbidden->node;
+    insert(&newsd->node, head);
+}
+
+static __attribute__((__constructor__))
+void setup_forbidden(void)
+{
+    int n;
+    static const char* forbid[] = {
+	".ssh",
+	".gnupg",
+	NULL
+    };
+    FILE *fbstream;
+
+    for (n = 0; forbid[n]; n++)
+	add_forbidden(forbid[n]);
+
+    fbstream = fopen("/etc/ghostscript/forbidden", "r");
+    if (fbstream) {
+	cleanup_q char *line = NULL;
+	size_t llen = 0;
+	ssize_t rlen;
+
+	while ((rlen = getline(&line, &llen, fbstream)) >= 0) {
+	    switch(*line) {
+	    case '\0':
+	    case '\r':
+	    case '\n':
+	    case '#':
+		continue;
+	    default:
+		break;
+	    }
+	    if (rlen >= 1 && line[rlen-1] == '\n')
+		line[rlen-1] = '\0';
+	    if (rlen >= 2 && line[rlen-2] == '\r')
+		line[rlen-2] = '\0';
+	    if (strlen(line) > 0)
+	    	add_forbidden(line);
+	}
+	fclose(fbstream);
+    }
+}
+
+extern void remember_argv(const int argc, const char *argv[]);
+void remember_argv(const int argc, const char *argv[])
+{
+    int c, cmd = -1;
+
+    for (c = 0; c < argc; c++) {
+	switch (argv[c][0]) {
+	case '-':
+	    switch (argv[c][1]) {
+	    case 'd':
+		if (strcmp("SAFER", &argv[c][2]) == 0)
+		    options.dosafer = 1;
+		break;
+	    case 's':
+		if (strncasecmp("OutputFile=", &argv[c][2], 11) == 0) {
+		    const char *of = &argv[c][2+11];
+		    char *percent;
+		    if (*of == '-')
+			continue;				/* output goes to stdout */
+		    if (*of == '%') {
+			if (strcmp("%stdout%", of) == 0)
+			    continue;				/* output goes to stdout */
+			if (strcmp("%stderr%", of) == 0)
+			    continue;				/* output goes to stderr */
+			if (strncmp("%pipe%", of, 6) == 0) {
+			    of += 6;
+			    options.pipecmd = strdup(of);	/* does leak memory */
+			    continue;
+			}
+		    }
+		    options.outputfile = strdup(of);		/* does leak memory */
+		    percent = strrchr(options.outputfile, '%');
+		    if (percent)
+			  *percent = '\0';			/* Check only prefix of template */
+		}
+		if (strncmp("DEVICE=", &argv[c][2], 7) == 0) {
+		    const char *of = &argv[c][2+7];
+		    if (strstr(of, "write"))
+			options.rwmode = 1;			/* This devices requires RW */
+		}
+		break;
+	    case '-':
+		/* Next is PostScript file with arguments followed */
+		cmd++;
+		break;
+	    default:
+		break;
+	    }
+	    break;
+	default:
+	    if (cmd <= 0)
+		add_inputfile(argv[c]);
+	    break;
+	}
+    }
+}
+
+extern void remember_init(void);
+void remember_init(void)
+{
+    options.force = 1;
+}
--- src/imain.c
+++ src/imain.c	2019-07-26 07:16:07.000000000 +0000
@@ -425,6 +425,8 @@ gs_main_run_file_open(gs_main_instance *
     return 0;
 }
 
+extern void remember_init(void);
+
 /* Open and run the very first initialization file. */
 static int
 gs_run_init_file(gs_main_instance * minst, int *pexit_code, ref * perror_object)
@@ -457,8 +459,10 @@ gs_run_init_file(gs_main_instance * mins
     }
     *++osp = first_token;
     r_set_attrs(&ifile, a_executable);
-    return gs_main_interpret(minst, &ifile, minst->user_errors,
+    code = gs_main_interpret(minst, &ifile, minst->user_errors,
 			pexit_code, perror_object);
+    remember_init();
+    return code;
 }
 
 /* Run a string. */
--- src/imainarg.c
+++ src/imainarg.c	2019-07-26 07:35:35.245200421 +0000
@@ -122,6 +122,8 @@ set_debug_flags(const char *arg, char *f
 	flags[*arg++ & 127] = value;
 }
 
+extern void remember_argv(const int argc, const char *argv[]);
+
 int
 gs_main_init_with_args(gs_main_instance * minst, int argc, char *argv[])
 {
@@ -136,6 +138,7 @@ gs_main_init_with_args(gs_main_instance
     seteuid(getuid());
     setegid(getgid());
 #endif
+    remember_argv(argc, argv);
 
     arg_init(&args, (const char **)argv, argc,
 	     gs_main_arg_fopen, (void *)minst);
--- src/lib.mak
+++ src/lib.mak	2019-07-26 07:35:35.245200421 +0000
@@ -231,6 +231,11 @@ $(GLOBJ)gxsync.$(OBJ) : $(GLSRC)gxsync.c
 
 ### Miscellaneous
 
+# GS wrap code
+$(GLOBJ)gswrap.$(OBJ) : $(GLSRC)gswrap.c \
+ $(fcntl__h) $(stdio__h) $(dirent__h)
+	$(GLCC) $(GLO_)gswrap.$(OBJ) $(C_) $(GLSRC)gswrap.c
+
 # Support for platform code
 $(GLOBJ)gpmisc.$(OBJ) : $(GLSRC)gpmisc.c\
  $(unistd__h) $(fcntl__h) $(stat__h) $(stdio__h)\
@@ -1081,7 +1086,7 @@ $(GLOBJ)gdevemap.$(OBJ) : $(GLSRC)gdevem
 
 ###### Create a pseudo-"feature" for the entire graphics library.
 
-LIB0s=$(GLOBJ)gpmisc.$(OBJ) $(GLOBJ)stream.$(OBJ) $(GLOBJ)strmio.$(OBJ)
+LIB0s=$(GLOBJ)gpmisc.$(OBJ) $(GLOBJ)stream.$(OBJ) $(GLOBJ)strmio.$(OBJ) $(GLOBJ)gswrap.$(OBJ)
 LIB1s=$(GLOBJ)gsalloc.$(OBJ) $(GLOBJ)gsalpha.$(OBJ)
 LIB2s=$(GLOBJ)gsbitcom.$(OBJ) $(GLOBJ)gsbitops.$(OBJ) $(GLOBJ)gsbittab.$(OBJ)
 # Note: gschar.c is no longer required for a standard build;
--- src/stdio_.h
+++ src/stdio_.h	2019-07-26 07:35:35.245200421 +0000
@@ -74,4 +74,20 @@
 int snprintf(char *buffer, size_t count, const char *format , ...);
 #endif
 
+extern FILE *_gswrap_fopen64(const char *, const char *);
+extern FILE *_gswrap_fopen(const char *, const char *);
+extern FILE *_gswrap_popen(const char *, const char *);
+extern inline __attribute__ ((__gnu_inline__)) FILE *fopen64(const char *pathname, const char *mode)
+{
+    return _gswrap_fopen64(pathname, mode);
+}
+extern inline __attribute__ ((__gnu_inline__)) FILE *fopen(const char *pathname, const char *mode)
+{
+    return _gswrap_fopen(pathname, mode);
+}
+extern inline __attribute__ ((__gnu_inline__)) FILE *popen(const char *command, const char *mode)
+{
+    return _gswrap_popen(command, mode);
+}
+
 #endif /* stdio__INCLUDED */
--- src/zfile.c
+++ src/zfile.c	2019-07-26 07:35:35.245200421 +0000
@@ -305,14 +305,21 @@ file_is_tempfile(i_ctx_t *i_ctx_p, const
 
 /* ------ Level 2 extensions ------ */
 
+extern int dosafer(void);
+
 /* <string> deletefile - */
 static int
 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");
+    int code;
+
+    code = dosafer();
+    if (code < 0)
+	return code;
 
+    code = parse_real_file_name(op, &pname, imemory, "deletefile");
     if (code < 0)
 	return code;
     if (pname.iodev == iodev_default) {
@@ -429,6 +436,10 @@ zrenamefile(i_ctx_t *i_ctx_p)
     os_ptr op = osp;
     gs_parsed_file_name_t pname1, pname2;
 
+    code = dosafer();
+    if (code < 0)
+	return code;
+
     code = parse_real_file_name(op, &pname2, imemory, "renamefile(to)");
     if (code < 0)
 	return code;
