CVE-2022-45866: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: qpress before PierreLvx/qpress 20220819 and before version 11.3, as used in Percona XtraBackup and other products, allows directory traversal via ../ in a .qp file.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 5.3
Published: 2022-11-23T20:15Z
Repository: https://github.com/EvgeniyPatlan/qpress
Language: C++
Files changed: qpress.cpp
Commit date: 2022-08-19 23:33:18 +0200
Commit message: Fix qpress directory traversal vulnerability (#6)

A bad actor user can prepare the payload as:

```
mkdir -p AAAAAAAAA/secure_file_priv_dir
touch AAAAAAAAA/secure_file_priv_dir/evil.so
qpress -r AAAAAAAAA payload.qp
Then edit the payload.qp in a hex editor or sed to replace AAAAAAAAA with ../../../
(example: sed -i 's/AAAAAAAAA/..\/..\/..\//' payload.qp)
```

Fix bug by checking the directory and reject the command if find the attempt to traversal

Test: see example above and try to reproduce it. Before fix you can observe
traversal. After fix - the error message(File path contains directory traversal
which is not allowed.) shown, no traversal observe.

All new code of the whole pull request, including one or several files
that are either new files or modified ones, are contributed under the BSD-new
license.  I am contributing on behalf of my employer Amazon Web Services,
Inc.

Co-authored-by: Mikhail Chalov <mcchalov@amazon.com>
Diff: diff --git a/qpress.cpp b/qpress.cpp
index cb10b6d..432e5b5 100755
--- a/qpress.cpp
+++ b/qpress.cpp
@@ -181,11 +181,13 @@ unsigned long long bench_size;
 
 // various
 unsigned long long recovery_file_written;
 unsigned long long recovery_bad_bytes = 0;
 unsigned long long current_file_payload;
-char tmp[200000];
+const long NAME_BUFFER_SIZE = 200000;
+char tmp[NAME_BUFFER_SIZE];
+
 
 enum {FATAL_ERROR, COUNTER_UPDATE, FILES_PROCESSED, RESULT, WARNING};
 
 void PRINT(int MESSAGE_TYPE, const char *fmt, ...)
 {
@@ -933,10 +935,25 @@ void compress_directory(string base_dir, string pattern)
 		}
 		closedir(dir);
 	}
  }
 
+bool check_if_path_has_dir_traversal(char * new_path)
+{
+    bool ret_val = false;
+    char *found_template1 = NULL;
+	found_template1 = strstr (new_path,"../");
+	#ifdef WINDOWS 
+	    char *found_template2 = NULL;
+	    found_template2 = strstr (new_path,"..\\");
+	    ret_val = (found_template1 !=  NULL) || (found_template2 != NULL);
+	#else
+	    ret_val = found_template1 !=  NULL;
+	#endif
+    
+    return ret_val;
+}
 
 void decompress_directory(string extract_dir, bool std_out)
 {
     char c;
     string curdir;
@@ -953,13 +970,25 @@ void decompress_directory(string extract_dir, bool std_out)
 
         if(c == 'D')
         {
 			// read directory name, append it to current path and create the directory
             chunk_size = fread32();
+			if(NAME_BUFFER_SIZE < chunk_size + 1)
+            {
+                abort("File path string is bigger than buffer size");
+            }
             try_aread(tmp, chunk_size + 1);
             curdir = curdir + DELIM_STR + tmp;
             PRINT(FILES_PROCESSED, "%s%s%s\n", BLANK_LINE, remove_leading_curdir(curdir).c_str(), DELIM_STR);
+			/* RDS security fix: https://rds-jira.amazon.com/browse/MMCP-231
+	 		Need to check if we try to decompress file or directory outside working directory
+	 		*/
+	        if(true == check_if_path_has_dir_traversal(tmp)) 
+            {
+	            abort("Directory path contains directory traversal which is not allowed.");
+	        }
+	        /* End of RDS security fix*/
             if(!std_out)
             {
 #ifdef WINDOWS
                 CreateDirectory(curdir.c_str(), 0);
 #else
@@ -970,11 +999,23 @@ void decompress_directory(string extract_dir, bool std_out)
         else if(c == 'U')
             curdir = curdir.substr(0, curdir.find_last_of(DELIM_STR)); // remove last sub dir from current path
         else if(c == 'F')
         {
             chunk_size = fread32(); // read length of file name
-            try_aread(tmp, chunk_size + 1); // read file name
+            if(NAME_BUFFER_SIZE < chunk_size + 1)
+	             {
+	                 abort("File path string is bigger than buffer size");
+	             }
+	             try_aread(tmp, chunk_size + 1); // read file name to 'tmp' with size 'chunk_size + 1'
+	         /* RDS security fix: https://rds-jira.amazon.com/browse/MMCP-231
+	 		   Need to check if we try to decompress file or directory outside working directory
+	 		*/
+	         if(true == check_if_path_has_dir_traversal(tmp)) 
+	         {
+	             abort("File path contains directory traversal which is not allowed.");
+	         }
+	         /* End of RDS security fix*/
             string buf2 = curdir + DELIM_STR + tmp;
             PRINT(FILES_PROCESSED, "%s    %s\n", BLANK_LINE, tmp);
             if(!std_out)
             {
                 if(exists(buf2) && !force_flag)

Vulnerable code: char tmp[200000];
            try_aread(tmp, chunk_size + 1); // read file name
Fixed code: const long NAME_BUFFER_SIZE = 200000;
char tmp[NAME_BUFFER_SIZE];

bool check_if_path_has_dir_traversal(char * new_path)
{
    bool ret_val = false;
    char *found_template1 = NULL;
	found_template1 = strstr (new_path,"../");
	#ifdef WINDOWS 
	    char *found_template2 = NULL;
	    found_template2 = strstr (new_path,"..\\");
	    ret_val = (found_template1 !=  NULL) || (found_template2 != NULL);
	#else
	    ret_val = found_template1 !=  NULL;
	#endif
    
    return ret_val;
}
			if(NAME_BUFFER_SIZE < chunk_size + 1)
            {
                abort("File path string is bigger than buffer size");
            }
			/* RDS security fix: https://rds-jira.amazon.com/browse/MMCP-231
	 		Need to check if we try to decompress file or directory outside working directory
	 		*/
	        if(true == check_if_path_has_dir_traversal(tmp)) 
            {
	            abort("Directory path contains directory traversal which is not allowed.");
	        }
	        /* End of RDS security fix*/
            if(NAME_BUFFER_SIZE < chunk_size + 1)
	             {
	                 abort("File path string is bigger than buffer size");
	             }
	             try_aread(tmp, chunk_size + 1); // read file name to 'tmp' with size 'chunk_size + 1'
	         /* RDS security fix: https://rds-jira.amazon.com/browse/MMCP-231
	 		   Need to check if we try to decompress file or directory outside working directory
	 		*/
	         if(true == check_if_path_has_dir_traversal(tmp)) 
	         {
	             abort("File path contains directory traversal which is not allowed.");
	         }
	         /* End of RDS security fix*/