From dda81f0505217a95db065e6bf9cc2d81eb902417 Mon Sep 17 00:00:00 2001
From: Stanislav Malyshev <stas@php.net>
Date: Tue, 4 Aug 2015 14:00:29 -0700
Subject: [PATCH] Fix bug #70019 - limit extracted files to given directory

---
 ext/phar/phar_object.c       |  50 +++++++++++++++++++++++++++++++++++++++----
 ext/phar/tests/bug70019.phpt |  22 +++++++++++++++++++
 ext/phar/tests/bug70019.zip  | Bin 0 -> 184 bytes
 3 files changed, 68 insertions(+), 4 deletions(-)
 create mode 100644 ext/phar/tests/bug70019.phpt
 create mode 100644 ext/phar/tests/bug70019.zip

Index: php5-5.3.3.1/ext/phar/phar_object.c
===================================================================
--- php5-5.3.3.1.orig/ext/phar/phar_object.c	2015-10-20 11:40:42.000000000 +0200
+++ php5-5.3.3.1/ext/phar/phar_object.c	2015-10-20 11:40:42.000000000 +0200
@@ -4204,6 +4204,9 @@
 	php_stream *fp;
 	char *fullpath, *slash;
 	mode_t mode;
+	cwd_state new_state;
+	char *filename;
+	size_t filename_len;
 
 	if (entry->is_mounted) {
 		/* silently ignore mounted entries */
@@ -4213,8 +4216,39 @@
 	if (entry->filename_len >= sizeof(".phar")-1 && !memcmp(entry->filename, ".phar", sizeof(".phar")-1)) {
 		return SUCCESS;
 	}
+	/* strip .. from path and restrict it to be under dest directory */
+	new_state.cwd = (char*)malloc(2);
+	new_state.cwd[0] = DEFAULT_SLASH;
+	new_state.cwd[1] = '\0';
+	new_state.cwd_length = 1;
+	if (virtual_file_ex(&new_state, entry->filename, NULL, CWD_EXPAND TSRMLS_CC) != 0 ||
+			new_state.cwd_length <= 1) {
+		if (EINVAL == errno && entry->filename_len > 50) {
+			char *tmp = estrndup(entry->filename, 50);
+			spprintf(error, 4096, "Cannot extract \"%s...\" to \"%s...\", extracted filename is too long for filesystem", tmp, dest);
+			efree(tmp);
+		} else {
+			spprintf(error, 4096, "Cannot extract \"%s\", internal error", entry->filename);
+		}
+		free(new_state.cwd);
+		return FAILURE;
+	}
+	filename = new_state.cwd + 1;
+	filename_len = new_state.cwd_length - 1;
+#ifdef PHP_WIN32
+	/* unixify the path back, otherwise non zip formats might be broken */
+	{
+		int cnt = filename_len;
+
+		do {
+			if ('\\' == filename[cnt]) {
+				filename[cnt] = '/';
+			}
+		} while (cnt-- >= 0);
+	}
+#endif
 
-	len = spprintf(&fullpath, 0, "%s/%s", dest, entry->filename);
+	len = spprintf(&fullpath, 0, "%s/%s", dest, filename);
 
 	if (len >= MAXPATHLEN) {
 		char *tmp;
@@ -4228,18 +4262,21 @@
 			spprintf(error, 4096, "Cannot extract \"%s\" to \"%s...\", extracted filename is too long for filesystem", entry->filename, fullpath);
 		}
 		efree(fullpath);
+		free(new_state.cwd);
 		return FAILURE;
 	}
 
 	if (!len) {
 		spprintf(error, 4096, "Cannot extract \"%s\", internal error", entry->filename);
 		efree(fullpath);
+		free(new_state.cwd);
 		return FAILURE;
 	}
 
 	if (PHAR_OPENBASEDIR_CHECKPATH(fullpath)) {
 		spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", openbasedir/safe mode restrictions in effect", entry->filename, fullpath);
 		efree(fullpath);
+		free(new_state.cwd);
 		return FAILURE;
 	}
 
@@ -4247,14 +4284,15 @@
 	if (!overwrite && SUCCESS == php_stream_stat_path(fullpath, &ssb)) {
 		spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", path already exists", entry->filename, fullpath);
 		efree(fullpath);
+		free(new_state.cwd);
 		return FAILURE;
 	}
 
 	/* perform dirname */
-	slash = zend_memrchr(entry->filename, '/', entry->filename_len);
+	slash = zend_memrchr(filename, '/', filename_len);
 
 	if (slash) {
-		fullpath[dest_len + (slash - entry->filename) + 1] = '\0';
+		fullpath[dest_len + (slash - filename) + 1] = '\0';
 	} else {
 		fullpath[dest_len] = '\0';
 	}
@@ -4264,23 +4302,27 @@
 			if (!php_stream_mkdir(fullpath, entry->flags & PHAR_ENT_PERM_MASK,  PHP_STREAM_MKDIR_RECURSIVE, NULL)) {
 				spprintf(error, 4096, "Cannot extract \"%s\", could not create directory \"%s\"", entry->filename, fullpath);
 				efree(fullpath);
+				free(new_state.cwd);
 				return FAILURE;
 			}
 		} else {
 			if (!php_stream_mkdir(fullpath, 0777,  PHP_STREAM_MKDIR_RECURSIVE, NULL)) {
 				spprintf(error, 4096, "Cannot extract \"%s\", could not create directory \"%s\"", entry->filename, fullpath);
 				efree(fullpath);
+				free(new_state.cwd);
 				return FAILURE;
 			}
 		}
 	}
 
 	if (slash) {
-		fullpath[dest_len + (slash - entry->filename) + 1] = '/';
+		fullpath[dest_len + (slash - filename) + 1] = '/';
 	} else {
 		fullpath[dest_len] = '/';
 	}
 
+	filename = NULL;
+	free(new_state.cwd);
 	/* it is a standalone directory, job done */
 	if (entry->is_dir) {
 		efree(fullpath);
