CVE-2020-15121 (published 2020-07-20T18:15Z)

In radare2 before version 4.5.0, malformed PDB file names in the PDB server path cause shell injection. To trigger the problem it's required to open the executable in radare2 and run idpd to trigger the download. The shell code will execute, and will create a file called pwned in the current directory.

Severity: MEDIUM
CVSS v2 base score: 6.8 (AV:N/AC:M/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.6, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-78
cwe_name: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
description: The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
extended_description: 
		   This weakness can lead to a vulnerability in environments in which the attacker does not have direct access to the operating system, such as in web applications. Alternately, if the weakness occurs in a privileged program, it could allow the attacker to specify commands that normally would not be accessible, or to call alternate commands with privileges that the attacker does not have. The problem is exacerbated if the compromised process does not follow the principle of least privilege, because the attacker-controlled commands may run with special system privileges that increases the amount of damage.
            There are at least two subtypes of OS command injection:
               
				 The application intends to execute a single, fixed program that is under its own control. It intends to use externally-supplied inputs as arguments to that program. For example, the program might use system("nslookup [HOSTNAME]") to run nslookup and allow the user to supply a HOSTNAME, which is used as an argument. Attackers cannot prevent nslookup from executing. However, if the program does not remove command separators from the HOSTNAME argument, attackers could place the separators into the arguments, which allows them to execute their own program after nslookup has finished executing.
				 The application accepts an input that it uses to fully select which program to run, as well as which commands to use. The application simply redirects this entire command to the operating system. For example, the program might use "exec([COMMAND])" to execute the [COMMAND] that was supplied by the user. If the COMMAND is under attacker control, then the attacker can execute arbitrary commands or programs. If the command is being executed using functions like exec() and CreateProcess(), the attacker might not be able to combine multiple commands together in the same line.
               
            From a weakness standpoint, these variants represent distinct programmer errors. In the first variant, the programmer clearly intends that input from untrusted parties will be part of the arguments in the command to be executed. In the second variant, the programmer does not intend for the command to be accessible to any untrusted party, but the programmer probably has not accounted for alternate ways in which malicious attackers can provide input.
         
         

Fix information:
repo_url: https://github.com/radareorg/radare2
commit_details: repo_url: https://github.com/radareorg/radare2
author: GustavoLCR
author_date: 2020-06-10 13:00:00-03:00
msg: Fix command injection on PDB download (#16966)

* Fix r_sys_mkdirp with absolute path on Windows
* Fix build with --with-openssl
* Use RBuffer in r_socket_http_answer()
* r_socket_http_answer: Fix read for big responses
* Implement r_str_escape_sh()
* Cleanup r_socket_connect() on Windows
* Fix socket being created without a protocol
* Fix socket connect with SSL ##socket
* Use select() in r_socket_ready()
* Fix read failing if received only protocol answer
* Fix double-free
* r_socket_http_get: Fail if req. SSL with no support
* Follow redirects in r_socket_http_answer()
* Fix r_socket_http_get result length with R2_CURL=1
* Also follow redirects
* Avoid using curl for downloading PDBs
* Use r_socket_http_get() on UNIXs
* Use WinINet API on Windows for r_socket_http_get()
* Fix command injection
* Fix r_sys_cmd_str_full output for binary data
* Validate GUID on PDB download
* Pass depth to socket_http_get_recursive()
* Remove 'r_' and '__' from static function names
* Fix is_valid_guid
* Fix for comments
repository: repo_url: https://github.com/radareorg/radare2
repo_name: radareorg/radare2
description: UNIX-like reverse engineering framework and command-line toolset
repo_language: C
owner: radareorg
file_changes: filename: pdb_downloader.c
change_type: ModificationType.MODIFY
diff: @@ -18,150 +18,107 @@ static bool checkExtract() {
 	return true;
 }
 
-static bool checkCurl() {
-	const char nul[] = R_SYS_DEVNULL;
-	if (r_sys_cmdf ("curl --version > %s", nul) != 0) {
+static bool download_and_write(SPDBDownloaderOpt *opt, const char *file) {
+	char *dir = r_str_newf ("%s%s%s%s%s",
+		opt->symbol_store_path, R_SYS_DIR,
+		opt->dbg_file, R_SYS_DIR,
+		opt->guid);
+	if (!r_sys_mkdirp (dir)) {
+		free (dir);
 		return false;
 	}
+	char *url = r_str_newf ("%s/%s/%s/%s", opt->symbol_server, opt->dbg_file, opt->guid, file);
+	int len;
+	char *file_buf = r_socket_http_get (url, NULL, &len);
+	free (url);
+	if (!len || R_STR_ISEMPTY (file_buf)) {
+		free (dir);
+		free (file_buf);
+		return false;
+	}
+	char *path = r_str_newf ("%s%s%s", dir, R_SYS_DIR, opt->dbg_file);
+	FILE *f = fopen (path, "wb");
+	if (f) {
+		fwrite (file_buf, sizeof (char), (size_t)len, f);
+		fclose (f);
+	}
+	free (dir);
+	free (path);
+	free (file_buf);
 	return true;
 }
 
 static int download(struct SPDBDownloader *pd) {
 	SPDBDownloaderOpt *opt = pd->opt;
-	char *curl_cmd = NULL;
-	char *extractor_cmd = NULL;
-	char *abspath_to_archive = NULL;
-	char *abspath_to_file = NULL;
-	char *archive_name = NULL;
-	size_t archive_name_len = 0;
-	char *symbol_store_path = NULL;
-	char *dbg_file = NULL;
-	char *guid = NULL;
-	char *archive_name_escaped  = NULL;
-	char *user_agent = NULL;
-	char *symbol_server = NULL;
-
 	int res = 0;
 	int cmd_ret;
+
 	if (!opt->dbg_file || !*opt->dbg_file) {
 		// no pdb debug file
 		return 0;
 	}
-	if (!checkCurl ()) {
-		return 0;
-	}
-	// dbg_file len is > 0
-	archive_name_len = strlen (opt->dbg_file);
-	archive_name = malloc (archive_name_len + 1);
-	if (!archive_name) {
-		return 0;
-	}
-	memcpy (archive_name, opt->dbg_file, archive_name_len + 1);
-	archive_name[archive_name_len - 1] = '_';
-	symbol_store_path = r_str_escape (opt->symbol_store_path);
-	dbg_file = r_str_escape (opt->dbg_file);
-	guid = r_str_escape (opt->guid);
-	archive_name_escaped = r_str_escape (archive_name);
-	user_agent = r_str_escape (opt->user_agent);
-	symbol_server = r_str_escape (opt->symbol_server);
-
-	abspath_to_archive = r_str_newf ("%s%s%s%s%s%s%s",
-			    symbol_store_path, R_SYS_DIR,
-			    dbg_file, R_SYS_DIR,
-			    guid, R_SYS_DIR,
-			    archive_name_escaped);
-
-	abspath_to_file = strdup (abspath_to_archive);
-	abspath_to_file[strlen (abspath_to_file) - 1] = 'b';
+
+	char *abspath_to_file = r_str_newf ("%s%s%s%s%s%s%s",
+		opt->symbol_store_path, R_SYS_DIR,
+		opt->dbg_file, R_SYS_DIR,
+		opt->guid, R_SYS_DIR,
+		opt->dbg_file);
+
 	if (r_file_exists (abspath_to_file)) {
 		eprintf ("File already downloaded.\n");
-		R_FREE (user_agent);
-		R_FREE (abspath_to_archive);
-		R_FREE (archive_name_escaped);
-		R_FREE (symbol_store_path);
-		R_FREE (dbg_file);
-		R_FREE (guid);
-		R_FREE (archive_name);
-		R_FREE (abspath_to_file);
-		R_FREE (symbol_server);
+		free (abspath_to_file);
 		return 1;
 	}
 
 	if (checkExtract () || opt->extract == 0) {
-		res = 1;
-
-		curl_cmd = r_str_newf ("curl -sfLA \"%s\" \"%s/%s/%s/%s\" --create-dirs -o \"%s\"",
-		                       user_agent,
-		                       symbol_server,
-							   dbg_file,
-							   guid,
-		                       archive_name_escaped,
-		                       abspath_to_archive);
+		char *extractor_cmd = NULL;
+		char *archive_name = strdup (opt->dbg_file);
+		archive_name[strlen (archive_name) - 1] = '_';
+		char *abspath_to_archive = r_str_newf ("%s%s%s%s%s%s%s",
+			opt->symbol_store_path, R_SYS_DIR,
+			opt->dbg_file, R_SYS_DIR,
+			opt->guid, R_SYS_DIR,
+			archive_name);
+
+		eprintf ("Attempting to download compressed pdb in %s\n", abspath_to_archive);
+		char *abs_arch_esc = r_str_escape_sh (abspath_to_archive);
 #if __WINDOWS__
-		const char *cabextractor = "expand";
-		const char *format = "%s %s %s";
-
-		// extractor_cmd -> %1 %2 %3
-		// %1 - 'expand'
-		// %2 - absolute path to archive
-		// %3 - absolute path to file that will be dearchive
-		extractor_cmd = r_str_newf (format, cabextractor,
-			abspath_to_archive, abspath_to_file);
+		char *abs_file_esc = r_str_escape_sh (abspath_to_file);
+		// expand %1 %2
+		// %1 - absolute path to archive
+		// %2 - absolute path to file that will be dearchive
+		extractor_cmd = r_str_newf ("expand \"%s\" \"%s\"", abs_arch_esc, abs_file_esc);
+		free (abs_file_esc);
 #else
-		const char *cabextractor = "cabextract";
-		const char *format = "%s -d \"%s\" \"%s\"";
 		char *abspath_to_dir = r_file_dirname (abspath_to_archive);
+		char *abs_dir_esc = r_str_escape_sh (abspath_to_dir);
 		// cabextract -d %1 %2
 		// %1 - path to directory where to extract all files from cab archive
 		// %2 - absolute path to cab archive
-		extractor_cmd = r_str_newf (format, cabextractor, abspath_to_dir, abspath_to_archive);
-		R_FREE (abspath_to_dir);
+		extractor_cmd = r_str_newf ("cabextract -d \"%s\" \"%s\"", abs_arch_esc, abs_dir_esc);
+		free (abs_dir_esc);
+		free (abspath_to_dir);
 #endif
-		eprintf ("Attempting to download compressed pdb in %s\n", abspath_to_archive);
-		if ((cmd_ret = r_sys_cmd (curl_cmd) != 0)) {
-			eprintf("curl exited with error %d\n", cmd_ret);
-			res = 0;
-		}
-		eprintf ("Attempting to decompress pdb\n");
-		if (opt->extract > 0) {
+		free (abs_arch_esc);
+		res = download_and_write (opt, archive_name);
+
+		if (opt->extract > 0 && res) {
+			eprintf ("Attempting to decompress pdb\n");
 			if (res && ((cmd_ret = r_sys_cmd (extractor_cmd)) != 0)) {
 				eprintf ("cab extractor exited with error %d\n", cmd_ret);
 				res = 0;
 			}
 			r_file_rm (abspath_to_archive);
 		}
-		R_FREE (curl_cmd);
+		free (archive_name);
+		free (abspath_to_archive);
 	}
 	if (res == 0) {
 		eprintf ("Falling back to uncompressed pdb\n");
-		res = 1;
-
-		archive_name_escaped[strlen (archive_name_escaped) - 1] = 'b';
-
-		curl_cmd = r_str_newf ("curl -sfLA \"%s\" \"%s/%s/%s/%s\" --create-dirs -o \"%s\"",
-		                       opt->user_agent,
-		                       opt->symbol_server,
-		                       opt->dbg_file,
-		                       opt->guid,
-		                       archive_name_escaped,
-		                       abspath_to_file);
 		eprintf ("Attempting to download uncompressed pdb in %s\n", abspath_to_file);
-		if ((cmd_ret = r_sys_cmd (curl_cmd) != 0)) {
-			eprintf("curl exited with error %d\n", cmd_ret);
-			res = 0;
-		}
-		R_FREE (curl_cmd);
-	}
-	R_FREE (abspath_to_archive);
-	R_FREE (abspath_to_file);
-	R_FREE (archive_name);
-	R_FREE (extractor_cmd);
-	R_FREE (symbol_store_path);
-	R_FREE (dbg_file);
-	R_FREE (guid);
-	R_FREE (archive_name_escaped);
-	R_FREE (user_agent);
-	R_FREE (symbol_server);
+		res = download_and_write (opt, opt->dbg_file);
+	}
+	free (abspath_to_file);
 	return res;
 }
 
@@ -191,6 +148,19 @@ void deinit_pdb_downloader(SPDBDownloader *pd) {
 	pd->download = 0;
 }
 
+static bool is_valid_guid(const char *guid) {
+	if (!guid) {
+		return false;
+	}
+	size_t i;
+	for (i = 0; guid[i]; i++) {
+		if (!isxdigit (guid[i])) {
+			return false;
+		}
+	}
+	return i >= 33; // len of GUID and age
+}
+
 int r_bin_pdb_download(RCore *core, int isradjson, int *actions_done, SPDBOptions *options) {
 	int ret;
 	SPDBDownloaderOpt opt;
@@ -202,6 +172,11 @@ int r_bin_pdb_download(RCore *core, int isradjson, int *actions_done, SPDBOption
 		return 1;
 	}
 
+	if (!is_valid_guid (info->guid)) {
+		eprintf ("Invalid GUID for file\n");
+		return 1;
+	}
+
 	if (!options || !options->symbol_server || !options->user_agent) {
 		eprintf ("Can't retrieve pdb configurations\n");
 		return 1;

programming_language: C
filename: config.mk.tail
change_type: ModificationType.MODIFY
diff: @@ -52,6 +52,11 @@ CFLAGS+=-DHAVE_LIB_GMP=1
 BN_LIBS=-lgmp
 endif
 
+# open-ssl
+ifeq (${HAVE_LIB_SSL},1)
+BN_LIBS=${SSL_LDFLAGS}
+endif
+
 #both of these need ssl includes
 ifneq (,$(filter r_socket r_util,$(BINDEPS)))
 ifeq (${HAVE_LIB_SSL},1)

programming_language: Shell
filename: r_socket.h
change_type: ModificationType.MODIFY
diff: @@ -1,11 +1,6 @@
 #ifndef R2_SOCKET_H
 #define R2_SOCKET_H
 
-/* Must be included before windows.h (r_types) */
-#if defined(__WINDOWS__)
-#include <ws2tcpip.h>
-#endif
-
 #include "r_types.h"
 #include "r_bind.h"
 #include "r_list.h"

programming_language: C
filename: r_types.h
change_type: ModificationType.MODIFY
diff: @@ -152,6 +152,7 @@
   #ifdef _MSC_VER
   /* Must be included before windows.h */
   #include <winsock2.h>
+  #include <ws2tcpip.h>
   #ifndef WIN32_LEAN_AND_MEAN
   #define WIN32_LEAN_AND_MEAN
   #endif

programming_language: C
filename: r_str.h
change_type: ModificationType.MODIFY
diff: @@ -148,6 +148,7 @@ R_API int r_str_path_unescape(char *path);
 R_API char *r_str_path_escape(const char *path);
 R_API int r_str_unescape(char *buf);
 R_API char *r_str_escape(const char *buf);
+R_API char *r_str_escape_sh(const char *buf);
 R_API char *r_str_escape_dot(const char *buf);
 R_API char *r_str_escape_latin1(const char *buf, bool show_asciidot, bool esc_bslash, bool colors);
 R_API char *r_str_escape_utf8(const char *buf, bool show_asciidot, bool esc_bslash);

programming_language: C
filename: socket.c
change_type: ModificationType.MODIFY
diff: @@ -260,80 +260,30 @@ R_API bool r_socket_spawn(RSocket *s, const char *cmd, unsigned int timeout) {
 R_API bool r_socket_connect(RSocket *s, const char *host, const char *port, int proto, unsigned int timeout) {
 	r_return_val_if_fail (s, false);
 #if __WINDOWS__
+#define gai_strerror gai_strerrorA
 	struct sockaddr_in sa;
-	struct hostent *he;
 	WSADATA wsadata;
-	TIMEVAL Timeout;
-	Timeout.tv_sec = timeout;
-	Timeout.tv_usec = 0;
 
 	if (WSAStartup (MAKEWORD (1, 1), &wsadata) == SOCKET_ERROR) {
 		eprintf ("Error creating socket.");
 		return false;
 	}
-	s->fd = socket (AF_INET, SOCK_STREAM, 0);
-	if (s->fd == R_INVALID_SOCKET) {
-		return false;
-	}
-
-	unsigned long iMode = 1;
-	int iResult = ioctlsocket (s->fd, FIONBIO, &iMode);
-	if (iResult != NO_ERROR) {
-		eprintf ("ioctlsocket error: %d\n", iResult);
-	}
-	memset (&sa, 0, sizeof (sa));
-	sa.sin_family = AF_INET;
-	he = (struct hostent *)gethostbyname (host);
-	if (he == (struct hostent*)0) {
-#ifdef _MSC_VER
-		closesocket (s->fd);
-#else
-		close (s->fd);
 #endif
-		return false;
-	}
-	sa.sin_addr = *((struct in_addr *)he->h_addr);
-	s->port = r_socket_port_by_name (port);
-	s->proto = proto;
-	sa.sin_port = htons (s->port);
-	if (!connect (s->fd, (const struct sockaddr*)&sa, sizeof (struct sockaddr))) {
-#ifdef _MSC_VER
-		closesocket (s->fd);
-#else
-		close (s->fd);
-#endif
-		return false;
-	}
-	iMode = 0;
-	iResult = ioctlsocket (s->fd, FIONBIO, &iMode);
-	if (iResult != NO_ERROR) {
-		eprintf ("ioctlsocket error: %d\n", iResult);
-	}
-	if (timeout > 0) {
-		r_socket_block_time (s, 1, timeout, 0);
-	}
-	fd_set Write, Err;
-	FD_ZERO (&Write);
-	FD_ZERO (&Err);
-	FD_SET (s->fd, &Write);
-	FD_SET (s->fd, &Err);
-	select (0, NULL, &Write, &Err, &Timeout);
-	if (FD_ISSET (s->fd, &Write)) {
-		return true;
-	}
-	return false;
-#elif __UNIX__
 	int ret;
-	struct addrinfo hints = {0};
+	struct addrinfo hints = { 0 };
 	struct addrinfo *res, *rp;
 	if (!proto) {
 		proto = R_SOCKET_PROTO_TCP;
 	}
+#if __UNIX__
 	r_sys_signal (SIGPIPE, SIG_IGN);
+#endif
 	if (proto == R_SOCKET_PROTO_UNIX) {
+#if __UNIX__
 		if (!__connect_unix (s, host)) {
 			return false;
 		}
+#endif
 	} else {
 		hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
 		hints.ai_protocol = proto;
@@ -367,17 +317,15 @@ R_API bool r_socket_connect(RSocket *s, const char *host, const char *port, int
 				return true;
 			}
 			if (errno == EINPROGRESS) {
-				struct timeval tv;
-				tv.tv_sec = timeout;
-				tv.tv_usec = 0;
+				struct timeval tv = {timeout, 0};
 				fd_set wfds;
-				FD_ZERO(&wfds);
-				FD_SET(s->fd, &wfds);
+				FD_ZERO (&wfds);
+				FD_SET (s->fd, &wfds);
 
 				if ((ret = select (s->fd + 1, NULL, &wfds, NULL, &tv)) != -1) {
 					if (r_socket_is_connected (s)) {
 						freeaddrinfo (res);
-						return true;
+						goto success;
 					}
 				} else {
 					perror ("connect");
@@ -391,18 +339,42 @@ R_API bool r_socket_connect(RSocket *s, const char *host, const char *port, int
 			return false;
 		}
 	}
-#endif
+success:
 #if HAVE_LIB_SSL
 	if (s->is_ssl) {
 		s->ctx = SSL_CTX_new (SSLv23_client_method ());
 		if (!s->ctx) {
-			r_socket_free (s);
+			r_socket_close (s);
 			return false;
 		}
 		s->sfd = SSL_new (s->ctx);
 		SSL_set_fd (s->sfd, s->fd);
-		if (SSL_connect (s->sfd) != 1) {
-			r_socket_free (s);
+		int ret = SSL_connect (s->sfd);
+		if (ret != 1) {
+			int error = SSL_get_error (s->sfd, ret);
+			int tries = 10;
+			while (tries && ret && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)) {
+				struct timeval tv = {1, 0};
+				fd_set rfds, wfds;
+				FD_ZERO (&rfds);
+				FD_ZERO (&wfds);
+				if (error == SSL_ERROR_WANT_READ) {
+					FD_SET (s->fd, &rfds);
+				} else {
+					FD_SET (s->fd, &wfds);
+				}
+				if ((ret = select (s->fd + 1, &rfds, &wfds, NULL, &tv)) < 1) {
+					r_socket_close (s);
+					return false;
+				}
+				ret = SSL_connect (s->sfd);
+				if (ret == 1) {
+					return true;
+				}
+				error = SSL_get_error (s->sfd, ret);
+				tries--;
+			}
+			r_socket_close (s);
 			return false;
 		}
 	}
@@ -622,9 +594,7 @@ R_API RSocket *r_socket_accept_timeout(RSocket *s, unsigned int timeout) {
 	FD_ZERO (&except_fds);
 	FD_SET (s->fd, &except_fds);
 
-	struct timeval t;
-	t.tv_sec = timeout;
-	t.tv_usec = 0;
+	struct timeval t = {timeout, 0};
 
 	int r = select (s->fd + 1, &read_fds, NULL, &except_fds, &t);
 	if(r < 0) {
@@ -659,9 +629,7 @@ R_API int r_socket_block_time(RSocket *s, int block, int sec, int usec) {
 	ioctlsocket (s->fd, FIONBIO, (u_long FAR*)&block);
 #endif
 	if (sec > 0 || usec > 0) {
-		struct timeval tv = {0};
-		tv.tv_sec = sec;
-		tv.tv_usec = usec;
+		struct timeval tv = {sec, usec};
 		if (setsockopt (s->fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof (tv)) < 0) {
 			return false;
 		}
@@ -678,32 +646,17 @@ R_API int r_socket_flush(RSocket *s) {
 	return true;
 }
 
-// XXX: rewrite it to use select //
 /* waits secs until new data is received.	  */
 /* returns -1 on error, 0 is false, 1 is true */
 R_API int r_socket_ready(RSocket *s, int secs, int usecs) {
-#if __UNIX__
-	//int msecs = (1000 * secs) + (usecs / 1000);
-	int msecs = (usecs / 1000);
-	struct pollfd fds[1];
-	fds[0].fd = s->fd;
-	fds[0].events = POLLIN | POLLPRI;
-	fds[0].revents = POLLNVAL | POLLHUP | POLLERR;
-	return poll ((struct pollfd *)&fds, 1, msecs);
-#elif __WINDOWS__
 	fd_set rfds;
-	struct timeval tv;
+	struct timeval tv = {secs, usecs};
 	if (s->fd == R_INVALID_SOCKET) {
 		return -1;
 	}
 	FD_ZERO (&rfds);
 	FD_SET (s->fd, &rfds);
-	tv.tv_sec = secs;
-	tv.tv_usec = usecs;
 	return select (s->fd + 1, &rfds, NULL, NULL, &tv);
-#else
-	return true; /* always ready if unknown */
-#endif
 }
 
 R_API char *r_socket_to_string(RSocket *s) {
@@ -794,21 +747,10 @@ R_API int r_socket_read(RSocket *s, unsigned char *buf, int len) {
 		return SSL_read (s->sfd, buf, len);
 	}
 #endif
-#if __WINDOWS__
-rep:
-	{
-	int ret = recv (s->fd, (void *)buf, len, 0);
-	if (ret == -1) {
-		goto rep;
-	}
-	return ret;
-	}
-#else
 	// int r = read (s->fd, buf, len);
 	int r = recv (s->fd, buf, len, 0);
 	D { eprintf ("READ "); int i; for (i = 0; i<len; i++) { eprintf ("%02x ", buf[i]); } eprintf ("\n"); }
 	return r;
-#endif
 }
 
 R_API int r_socket_read_block(RSocket *s, ut8 *buf, int len) {
@@ -816,6 +758,13 @@ R_API int r_socket_read_block(RSocket *s, ut8 *buf, int len) {
 	for (ret = 0; ret < len; ) {
 		int r = r_socket_read (s, buf + ret, len - ret);
 		if (r == -1) {
+#if HAVE_LIB_SSL
+			if (SSL_get_error (s->sfd, r) == SSL_ERROR_WANT_READ) {
+				if (r_socket_ready (s, 1, 0) == 1) {
+					continue;
+				}
+			}
+#endif
 			return -1;
 		}
 		if (r < 1) {

programming_language: C
filename: socket_http.c
change_type: ModificationType.MODIFY
diff: @@ -3,45 +3,81 @@
 #include <r_socket.h>
 #include <r_util.h>
 
-static int __socket_slurp (RSocket *s, ut8 *buf, int bufsz) {
-	int i;
-	int chsz = 1;
-	// r_socket_block_time (s, 1, 1, 0);
-	if (r_socket_read_block (s, (ut8 *) buf, 1) != 1) {
+#if __WINDOWS__
+#include <WinInet.h>
+#endif
+
+#define SOCKET_HTTP_MAX_HEADER_LENGTH 0x2000
+#define SOCKET_HTTP_MAX_REDIRECTS 5
+
+static size_t socket_slurp(RSocket *s, RBuffer *buf) {
+	size_t i;
+	if (r_socket_ready (s, 1, 0) != 1) {
 		return 0;
 	}
-	for (i = 1; i < bufsz; i += chsz) {
-		buf[i] =0;
-		r_socket_block_time (s, 1, 0, 1000);
-		int olen = r_socket_read_block (s, (ut8 *) buf + i , chsz);
-		if (olen != chsz) {
-			buf[i] = 0;
+	r_socket_block_time (s, 1, 0, 1000);
+	for (i = 0; i < SOCKET_HTTP_MAX_HEADER_LENGTH; i += 1) {
+		ut8 c;
+		int olen = r_socket_read_block (s, &c, 1);
+		if (olen != 1) {
+			r_buf_append_bytes (buf, (ut8 *)"", 1);
 			break;
 		}
+		r_buf_append_bytes (buf, &c, 1);
 	}
 	return i;
 }
 
-static char *r_socket_http_answer (RSocket *s, int *code, int *rlen) {
+static char *socket_http_get_recursive(const char *url, int *code, int *rlen, ut32 redirections);
+
+static char *socket_http_answer(RSocket *s, int *code, int *rlen, ut32 redirections) {
 	r_return_val_if_fail (s, NULL);
 	const char *p;
-	int ret, len = 0, bufsz = 32768, delta = 0;
-	char *dn, *buf = calloc (1, bufsz + 32); // XXX: use r_buffer here
-	if (!buf) {
+	int ret, len = 0, delta = 0;
+	char *dn;
+	RBuffer *b = r_buf_new ();
+	if (!b) {
 		return NULL;
 	}
 	char *res = NULL;
-	int olen = __socket_slurp (s, (ut8*)buf, bufsz);
+	size_t olen = socket_slurp (s, b);
+	char *buf = malloc (olen + 1);
+	if (!buf) {
+		goto exit;
+	}
+	r_buf_read_at (b, 0, (ut8 *)buf, olen);
+	buf[olen] = 0;
 	if ((dn = (char*)r_str_casestr (buf, "\n\n"))) {
 		delta += 2;
 	} else if ((dn = (char*)r_str_casestr (buf, "\r\n\r\n"))) {
 		delta += 4;
 	} else {
-		goto fail;
+		goto exit;
 	}
 
 	olen -= delta;
 	*dn = 0; // chop headers
+
+	/* Follow redirects */
+	p = r_str_casestr (buf, "Location:");
+	if (p) {
+		if (!redirections) {
+			eprintf ("Too many redirects\n");
+			goto exit;
+		}
+		p += strlen ("Location:");
+		char *end_url = strchr (p, '\n');
+		if (end_url) {
+			int url_len = end_url - p;
+			char *url = r_str_ndup (p, url_len);
+			r_str_trim (url);
+			res = socket_http_get_recursive (url, code, rlen, --redirections);
+			free (url);
+			len = *rlen;
+		}
+		goto exit;
+	}
+
 	/* Parse Len */
 	p = r_str_casestr (buf, "Content-Length: ");
 	if (p) {
@@ -52,6 +88,10 @@ static char *r_socket_http_answer (RSocket *s, int *code, int *rlen) {
 	if (len > 0) {
 		if (len > olen) {
 			res = malloc (len + 2);
+			if (!res) {
+				goto exit;
+			}
+			olen -= dn - buf;
 			memcpy (res, dn + delta, olen);
 			do {
 				ret = r_socket_read_block (s, (ut8*) res + olen, len - olen);
@@ -71,9 +111,9 @@ static char *r_socket_http_answer (RSocket *s, int *code, int *rlen) {
 	} else {
 		res = NULL;
 	}
-fail:
+exit:
 	free (buf);
-// is 's' free'd? isn't this going to cause a double free?
+	r_buf_free (b);
 	r_socket_close (s);
 	if (rlen) {
 		*rlen = len;
@@ -81,38 +121,108 @@ fail:
 	return res;
 }
 
-R_API char *r_socket_http_get(const char *url, int *code, int *rlen) {
+#if __WINDOWS__
+static char *http_get_w32(const char *url, int *code, int *rlen) {
+	HINTERNET hInternet = InternetOpenA ("radare2 "R2_VERSION, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
+	if (!hInternet) {
+		r_sys_perror ("InternetOpenA");
+		return NULL;
+	}
+	HINTERNET hOpenUrl = InternetOpenUrlA (hInternet, url, NULL, 0, 0, 0);
+	if (!hOpenUrl) {
+		r_sys_perror ("InternetOpenUrlA");
+		InternetCloseHandle (hInternet);
+		return NULL;
+	}
+
+	char *ret = NULL;
+	size_t read_sz = 0x100000;
+	DWORD r = 0, w = 0;
+	bool res = true;
+	do {
+		w += r;
+		if (!res && GetLastError () == ERROR_INSUFFICIENT_BUFFER) {
+			read_sz *= 2;
+		}
+		char *tmp = realloc (ret, read_sz + w);
+		if (!tmp) {
+			R_FREE (ret);
+			goto exit;
+		}
+		ret = tmp;
+	} while (!(res = InternetReadFile (hOpenUrl, ret + w, read_sz, &r)) || r);
+
+	if (w) {
+		char *tmp = realloc (ret, (size_t)w + 1);
+		if (tmp) {
+			ret = tmp;
+			ret[w] = 0;
+		} else {
+			R_FREE (ret);
+		}
+	} else {
+		R_FREE (ret);
+	}
+
+exit:
+	if (rlen) {
+		*rlen = w;
+	}
+	if (code && w) {
+		*code = 200;
+	}
+	InternetCloseHandle (hInternet);
+	InternetCloseHandle (hOpenUrl);
+	return ret;
+}
+#endif
+
+static char *socket_http_get_recursive(const char *url, int *code, int *rlen, ut32 redirections) {
+	if (code) {
+		*code = 0;
+	}
+	if (rlen) {
+		*rlen = 0;
+	}
 	char *curl_env = r_sys_getenv ("R2_CURL");
-	if (curl_env && *curl_env) {
-		char *encoded_url = r_str_escape (url);
-		char *res = r_sys_cmd_strf ("curl '%s'", encoded_url);
-		free (encoded_url);
+	if (!R_STR_ISEMPTY (curl_env) && atoi (curl_env)) {
+		int len;
+		char *escaped_url = r_str_escape_sh (url);
+		char *command = r_str_newf ("curl -sfL -o - \"%s\"", escaped_url);
+		char *res = r_sys_cmd_str (command, NULL, &len);
+		free (escaped_url);
+		free (command);
+		free (curl_env);
+		if (!res) {
+			return NULL;
+		}
 		if (res) {
 			if (code) {
 				*code = 200;
 			}
 			if (rlen) {
-				*rlen = strlen (res);
+				*rlen = len;
 			}
 		}
-		free (curl_env);
 		return res;
 	}
 	free (curl_env);
+#if __WINDOWS__
+	return http_get_w32 (url, code, rlen);
+#else
 	RSocket *s;
 	int ssl = r_str_startswith (url, "https://");
+#if !HAVE_LIB_SSL
+	if (ssl) {
+		eprintf ("Tried to get '%s', but SSL support is disabled, set R2_CURL=1 to use curl\n", url);
+		return NULL;
+	}
+#endif
 	char *response, *host, *path, *port = "80";
 	char *uri = strdup (url);
 	if (!uri) {
 		return NULL;
 	}
-
-	if (code) {
-		*code = 0;
-	}
-	if (rlen) {
-		*rlen = 0;
-	}
 	host = strstr (uri, "://");
 	if (!host) {
 		free (uri);
@@ -147,7 +257,7 @@ R_API char *r_socket_http_get(const char *url, int *code, int *rlen) {
 				"Accept: */*\r\n"
 				"Host: %s:%s\r\n"
 				"\r\n", path, host, port);
-		response = r_socket_http_answer (s, code, rlen);
+		response = socket_http_answer (s, code, rlen, redirections);
 	} else {
 		eprintf ("Cannot connect to %s:%s\n", host, port);
 		response = NULL;
@@ -155,9 +265,14 @@ R_API char *r_socket_http_get(const char *url, int *code, int *rlen) {
 	free (uri);
 	r_socket_free (s);
 	return response;
+#endif
+}
+
+R_API char *r_socket_http_get(const char *url, int *code, int *rlen) {
+	return socket_http_get_recursive (url, code, rlen, SOCKET_HTTP_MAX_REDIRECTS);
 }
 
-R_API char *r_socket_http_post (const char *url, const char *data, int *code, int *rlen) {
+R_API char *r_socket_http_post(const char *url, const char *data, int *code, int *rlen) {
 	RSocket *s;
 	bool ssl = r_str_startswith (url, "https://");
 	char *uri = strdup (url);
@@ -206,7 +321,7 @@ R_API char *r_socket_http_post (const char *url, const char *data, int *code, in
 			"\r\n", path, host, (int)strlen (data));
 	free (uri);
 	r_socket_write (s, (void *)data, strlen (data));
-	return r_socket_http_answer (s, code, rlen);
+	return socket_http_answer (s, code, rlen, 0);
 }
 
 #if TEST

programming_language: C
filename: str.c
change_type: ModificationType.MODIFY
diff: @@ -1351,6 +1351,34 @@ R_API char *r_str_escape(const char *buf) {
 	return r_str_escape_ (buf, false, true, true, false, true);
 }
 
+// Return MUST BE surrounded by double-quotes
+R_API char *r_str_escape_sh(const char *buf) {
+	r_return_val_if_fail (buf, NULL);
+	char *new_buf = malloc (1 + strlen (buf) * 2);
+	if (!new_buf) {
+		return NULL;
+	}
+	const char *p = buf;
+	char *q = new_buf;
+	while (*p) {
+		switch (*p) {
+#if __UNIX__
+		case '$':
+		case '`':
+#endif
+		case '\\':
+		case '"':
+			*q++ = '\\';
+			/* FALLTHRU */
+		default:
+			*q++ = *p++;
+			break;
+		}
+	}
+	*q = '\0';
+	return new_buf;
+}
+
 R_API char *r_str_escape_dot(const char *buf) {
 	return r_str_escape_ (buf, true, true, true, false, true);
 }

programming_language: C
filename: sys.c
change_type: ModificationType.MODIFY
diff: @@ -660,6 +660,7 @@ R_API int r_sys_cmd_str_full(const char *cmd, const char *input, char **output,
 		}
 		// we should handle broken pipes somehow better
 		r_sys_signal (SIGPIPE, SIG_IGN);
+		size_t err_len = 0, out_len = 0;
 		for (;;) {
 			fd_set rfds, wfds;
 			int nfd;
@@ -680,20 +681,29 @@ R_API int r_sys_cmd_str_full(const char *cmd, const char *input, char **output,
 				break;
 			}
 			if (output && FD_ISSET (sh_out[0], &rfds)) {
-				if (!(bytes = read (sh_out[0], buffer, sizeof (buffer)-1))) {
+				if ((bytes = read (sh_out[0], buffer, sizeof (buffer))) < 1) {
 					break;
 				}
-				buffer[sizeof (buffer) - 1] = '\0';
-				if (len) {
-					*len += bytes;
+				char *tmp = realloc (outputptr, out_len + bytes + 1);
+				if (!tmp) {
+					R_FREE (outputptr);
+					break;
 				}
-				outputptr = r_str_append (outputptr, buffer);
+				outputptr = tmp;
+				memcpy (outputptr + out_len, buffer, bytes);
+				out_len += bytes;
 			} else if (FD_ISSET (sh_err[0], &rfds) && sterr) {
-				if (!read (sh_err[0], buffer, sizeof (buffer)-1)) {
+				if ((bytes = read (sh_err[0], buffer, sizeof (buffer))) < 1) {
+					break;
+				}
+				char *tmp = realloc (*sterr, err_len + bytes + 1);
+				if (!tmp) {
+					R_FREE (*sterr);
 					break;
 				}
-				buffer[sizeof (buffer) - 1] = '\0';
-				*sterr = r_str_append (*sterr, buffer);
+				*sterr = tmp;
+				memcpy (*sterr + err_len, buffer, bytes);
+				err_len += bytes;
 			} else if (FD_ISSET (sh_in[1], &wfds) && inputptr && *inputptr) {
 				int inputptr_len = strlen (inputptr);
 				bytes = write (sh_in[1], inputptr, inputptr_len);
@@ -727,6 +737,15 @@ R_API int r_sys_cmd_str_full(const char *cmd, const char *input, char **output,
 			ret = false;
 		}
 
+		if (len) {
+			*len = out_len;
+		}
+		if (*sterr) {
+			(*sterr)[err_len] = 0;
+		}
+		if (outputptr) {
+			outputptr[out_len] = 0;
+		}
 		if (output) {
 			*output = outputptr;
 		} else {
@@ -829,7 +848,7 @@ R_API bool r_sys_mkdirp(const char *dir) {
 	{
 		char *p = strstr (ptr, ":\\");
 		if (p) {
-			ptr = p + 2;
+			ptr = p + 3;
 		}
 	}
 #endif

programming_language: C
filename: meson.build
change_type: ModificationType.MODIFY
diff: @@ -112,7 +112,7 @@ endif
 platform_deps = []
 platform_inc = ['.', 'libr/include']
 if host_machine.system() == 'windows'
-  platform_deps = [cc.find_library('ws2_32'), cc.find_library('psapi')]
+  platform_deps = [cc.find_library('ws2_32'), cc.find_library('wininet'), cc.find_library('psapi')]
 endif
 platform_inc = include_directories(platform_inc)
 

programming_language: Python
filename: disk.c
change_type: ModificationType.MODIFY
diff: @@ -64,7 +64,7 @@ static inline int r_sys_mkdirp(char *dir) {
 #if __SDB_WINDOWS__
 	char *p = strstr (ptr, ":\\");
 	if (p) {
-		ptr = p + 2;
+		ptr = p + 3;
 	}
 #endif
 	while ((ptr = strchr (ptr, slash))) {

programming_language: C
filename: pdb
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,9 @@
 NAME=PDB downloader check
 FILE=bins/pdb/user32.dll
-CMDS=!!rabin2 -PP ${R2_FILE} ~PDB
+CMDS=<<EOF
+%R2_CURL=1
+!!rabin2 -PP ${R2_FILE} ~PDB
+EOF
 EXPECT=<<EOF
 PDB "user32.pdb" download success
 EOF

programming_language: Shell
filename: test_str.c
change_type: ModificationType.MODIFY
diff: @@ -336,6 +336,24 @@ bool test_r_str_sanitize_sdb_key(void) {
 	mu_end;
 }
 
+bool test_r_str_escape_sh(void) {
+	char *escaped = r_str_escape_sh ("Hello, \"World\"");
+	mu_assert_streq (escaped, "Hello, \\\"World\\\"", "escaped \"double quotes\"");
+	free (escaped);
+	escaped = r_str_escape_sh ("Hello, \\World\\");
+	mu_assert_streq (escaped, "Hello, \\\\World\\\\", "escaped backspace");
+	free (escaped);
+#if __UNIX__
+	escaped = r_str_escape_sh ("Hello, $(World)");
+	mu_assert_streq (escaped, "Hello, \\$(World)", "escaped $(command)");
+	free (escaped);
+	escaped = r_str_escape_sh ("Hello, `World`");
+	mu_assert_streq (escaped, "Hello, \\`World\\`", "escaped `command`");
+	free (escaped);
+#endif
+	mu_end;
+}
+
 bool test_r_str_unescape(void) {
 	char buf[] = "Hello\\x31World\\n";
 	r_str_unescape (buf);
@@ -477,6 +495,7 @@ bool all_tests () {
 	mu_run_test (test_r_str_utf8_charsize);
 	mu_run_test (test_r_str_utf8_charsize_prev);
 	mu_run_test (test_r_str_sanitize_sdb_key);
+	mu_run_test (test_r_str_escape_sh);
 	mu_run_test (test_r_str_unescape);
 	mu_run_test (test_r_str_constpool);
 	mu_run_test (test_r_str_format_msvc_argv);

programming_language: C