DragonFlyBSD Kernel Audit
DF-2972 / fix.diff
← back to finding ↓ download raw
--- a/sys/kern/imgact_shell.c
+++ b/sys/kern/imgact_shell.c
@@ -30,6 +30,7 @@
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/sysmsg.h>
+#include <sys/malloc.h>
 #include <sys/exec.h>
 #include <sys/imgact.h>
 #include <sys/kernel.h>
@@ -49,6 +50,7 @@
 {
 	const char *image_header = imgp->image_header;
 	const char *ihp;
+	char *snap;			/* stable snapshot of the first page */
 	size_t length, offset;
 	int error;
 
@@ -66,6 +68,19 @@
 	imgp->interpreted = 1;
 
 	/*
+	 * Snapshot the first page ONCE.  The mapped first page is the
+	 * file's live page-cache page and can be modified concurrently
+	 * (e.g. via a MAP_SHARED mapping that outlives close(), which
+	 * also evades the ETXTBSY writecount check in
+	 * exec_check_permissions()).  Scanning the page twice allowed
+	 * the accounting pass below and the copy pass further down to
+	 * observe two different interpreter lines.
+	 */
+	snap = kmalloc(PAGE_SIZE, M_TEMP, M_WAITOK);
+	bcopy(image_header, snap, PAGE_SIZE);
+	image_header = snap;
+
+	/*
 	 * Figure out the number of bytes that need to be reserved in the
 	 * argument string to copy the contents of the interpreter's command
 	 * line into the argument string.
@@ -96,15 +111,19 @@
 	}
 
 	/* If the script gives a null line as the interpreter, we bail */
-	if (offset == 0)
+	if (offset == 0) {
+		kfree(snap, M_TEMP);
 		return (ENOEXEC);
+	}
 
 	/* It should not be possible for offset to exceed PAGE_SIZE */
 	KKASSERT(offset <= PAGE_SIZE);
 
 	/* Check that we aren't too big */
-	if (ihp == &image_header[PAGE_SIZE])
+	if (ihp == &image_header[PAGE_SIZE]) {
+		kfree(snap, M_TEMP);
 		return (ENAMETOOLONG);
+	}
 
 	/*
 	 * The full path name of the original script file must be tagged
@@ -117,8 +136,10 @@
 	offset += strlen(imgp->args->fname) + 1;	/* add fname */
 	length = strlen(imgp->args->begin_argv) + 1;	/* bytes to delete */
 
-	if (offset > imgp->args->space + length)
+	if (offset > imgp->args->space + length) {
+		kfree(snap, M_TEMP);
 		return (E2BIG);
+	}
 
 	bcopy(imgp->args->begin_argv + length, imgp->args->begin_argv + offset,
 		imgp->args->endp - (imgp->args->begin_argv + length));
@@ -173,6 +194,7 @@
 		error = copystr(imgp->args->begin_argv, imgp->interpreter_name,
 				MAXSHELLCMDLEN, &length);
 	}
+	kfree(snap, M_TEMP);
 	return (error);
 }