changeset 11277:15487697cc18 draft

(svn r15626) -Fix [FS#2698]: UTF8 string handling could cause buffer overruns.
author rubidium <rubidium@openttd.org>
date Fri, 06 Mar 2009 01:23:25 +0000
parents 687950e794be
children d1a6e82f03c5
files src/console.cpp src/fios.cpp src/network/core/packet.cpp src/saveload/oldloader.cpp src/saveload/saveload.cpp src/string.cpp src/string_func.h src/video/dedicated_v.cpp
diffstat 8 files changed, 37 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/src/console.cpp
+++ b/src/console.cpp
@@ -97,7 +97,7 @@
 	 * characters and (when applicable) assign it to the console buffer */
 	str = strdup(string);
 	str_strip_colours(str);
-	str_validate(str);
+	str_validate(str, str + strlen(str));
 
 	if (_network_dedicated) {
 		fprintf(stdout, "%s\n", str);
--- a/src/fios.cpp
+++ b/src/fios.cpp
@@ -247,7 +247,7 @@
 		t = (t == NULL) ? filename : (t + 1);
 	}
 	strecpy(fios->title, t, lastof(fios->title));
-	str_validate(fios->title);
+	str_validate(fios->title, lastof(fios->title));
 
 	return true;
 }
@@ -292,7 +292,7 @@
 				fios->mtime = 0;
 				strecpy(fios->name, d_name, lastof(fios->name));
 				snprintf(fios->title, lengthof(fios->title), "%s" PATHSEP " (Directory)", d_name);
-				str_validate(fios->title);
+				str_validate(fios->title, lastof(fios->title));
 			}
 		}
 		closedir(dir);
@@ -344,6 +344,7 @@
 	size_t read = fread(title, 1, last - title, f);
 	assert(title + read <= last);
 	title[read] = '\0';
+	str_validate(title, last);
 	FioFCloseFile(f);
 }
 
--- a/src/network/core/packet.cpp
+++ b/src/network/core/packet.cpp
@@ -237,6 +237,7 @@
 {
 	PacketSize pos;
 	char *bufp = buffer;
+	const char *last = buffer + size - 1;
 
 	/* Don't allow reading from a closed socket */
 	if (cs->HasClientQuit()) return;
@@ -253,7 +254,7 @@
 	}
 	this->pos = pos;
 
-	str_validate(bufp, allow_newlines);
+	str_validate(bufp, last, allow_newlines);
 }
 
 #endif /* ENABLE_NETWORK */
--- a/src/saveload/oldloader.cpp
+++ b/src/saveload/oldloader.cpp
@@ -232,6 +232,7 @@
 
 	bool ret = VerifyOldNameChecksum(temp, len);
 	temp[len - 2] = '\0'; // name is nul-terminated in savegame, but it's better to be sure
+	str_validate(temp, last);
 
 	return ret;
 }
--- a/src/saveload/saveload.cpp
+++ b/src/saveload/saveload.cpp
@@ -33,6 +33,7 @@
 #include "../statusbar_gui.h"
 #include "../fileio_func.h"
 #include "../gamelog.h"
+#include "../string_func.h"
 
 #include "table/strings.h"
 
@@ -631,6 +632,7 @@
 		}
 
 		((char*)ptr)[len] = '\0'; // properly terminate the string
+		str_validate((char*)ptr, (char*)ptr + len);
 	}
 }
 
--- a/src/string.cpp
+++ b/src/string.cpp
@@ -97,13 +97,27 @@
 }
 
 
-void str_validate(char *str, bool allow_newlines, bool ignore)
+void str_validate(char *str, const char *last, bool allow_newlines, bool ignore)
 {
+	/* Assume the ABSOLUTE WORST to be in str as it comes from the outside. */
+
 	char *dst = str;
-	WChar c;
-	size_t len;
+	while (*str != '\0') {
+		size_t len = Utf8EncodedCharLen(*str);
+		/* If the character is unknown, i.e. encoded length is 0
+		 * we assume worst case for the length check.
+		 * The length check is needed to prevent Utf8Decode to read
+		 * over the terminating '\0' if that happens to be placed
+		 * within the encoding of an UTF8 character. */
+		if ((len == 0 && str + 4 > last) || str + len > last) break;
 
-	for (len = Utf8Decode(&c, str); c != '\0'; len = Utf8Decode(&c, str)) {
+		WChar c;
+		len = Utf8Decode(&c, str);
+		/* It's possible to encode the string termination character
+		 * into a multiple bytes. This prevents those termination
+		 * characters to be skipped */
+		if (c == '\0') break;
+
 		if (IsPrintable(c) && (c < SCC_SPRITE_START || c > SCC_SPRITE_END)) {
 			/* Copy the character back. Even if dst is current the same as str
 			 * (i.e. no characters have been changed) this is quicker than
--- a/src/string_func.h
+++ b/src/string_func.h
@@ -93,9 +93,15 @@
 
 char *CDECL str_fmt(const char *str, ...);
 
-/** Scans the string for valid characters and if it finds invalid ones,
- * replaces them with a question mark '?' */
-void str_validate(char *str, bool allow_newlines = false, bool ignore = false);
+/**
+ * Scans the string for valid characters and if it finds invalid ones,
+ * replaces them with a question mark '?' (if not ignored)
+ * @param str the string to validate
+ * @param last the last valid character of str
+ * @param allow_newlines whether newlines should be allowed or ignored
+ * @param ignore whether to ignore or replace with a question mark
+ */
+void str_validate(char *str, const char *last, bool allow_newlines = false, bool ignore = false);
 
 /** Scans the string for colour codes and strips them */
 void str_strip_colours(char *str);
--- a/src/video/dedicated_v.cpp
+++ b/src/video/dedicated_v.cpp
@@ -235,7 +235,7 @@
 			break;
 		}
 	}
-	str_validate(input_line);
+	str_validate(input_line, lastof(input_line));
 
 	IConsoleCmdExec(input_line); // execute command
 }