File dhcp-3.1.1-dhclient-option-checks.bnc675052.diff of Package dhcp

Index: dhcp-3.1.1/client/dhclient.c
===================================================================
--- dhcp-3.1.1.orig/client/dhclient.c
+++ dhcp-3.1.1/client/dhclient.c
@@ -37,6 +37,8 @@ static char ocopyright[] =
 
 #include "dhcpd.h"
 #include "version.h"
+#include <stddef.h>
+#include <ctype.h>
 
 TIME default_lease_time = 43200; /* 12 hours... */
 TIME max_lease_time = 86400; /* 24 hours... */
@@ -76,6 +78,10 @@ int quiet=0;
 int nowait=0;
 
 static void usage PROTO ((void));
+static int check_domain_name(const char *ptr, size_t len, int dots);
+static int check_domain_name_list(const char *ptr, size_t len, int dots);
+static int check_option_values(struct universe *universe, unsigned int opt,
+			       const char *ptr, size_t len);
 
 int main (argc, argv, envp)
 	int argc;
@@ -197,8 +203,11 @@ int main (argc, argv, envp)
 				log_error("-H option host-name string \"%s\" is too long:"
 				          "maximum length is %d characters",  argv[i], HOST_NAME_MAX);
 				exit(1);
+			} else if (check_domain_name(argv [i], strlen(argv [i]), 0) != 0) {
+				log_error("suspect hostname in -H option - discarded");
+			} else {
+				dhclient_hostname = argv [i];
 			}
-			dhclient_hostname = argv [i];
 		} else if (!strcmp (argv [i], "-nw")) {
 			nowait = 1;
 		} else if (!strcmp (argv [i], "-n")) {
@@ -2543,12 +2552,21 @@ void client_option_envadd (struct option
 			char name [256];
 			if (dhcp_option_ev_name (name, sizeof name,
 						 oc -> option)) {
-				client_envadd (es -> client, es -> prefix,
-					       name, "%s",
-					       (pretty_print_option
-						(oc -> option,
-						 data.data, data.len,
-						 0, 0)));
+				const char *value = pretty_print_option(oc -> option,
+									data.data,
+									data.len, 0, 0);
+				size_t length = strlen(value);
+
+				if (check_option_values(oc->option->universe,
+							oc->option->code,
+							value, length) == 0) {
+					client_envadd (es -> client, es -> prefix,
+						       name, "%s", value);
+				} else {
+					log_error ("suspect value in %s "
+						   "option - discarded",
+						   name);
+				}
 				data_string_forget (&data, MDL);
 			}
 		}
@@ -2621,12 +2639,31 @@ void script_write_params (client, prefix
 		data_string_forget (&data, MDL);
 	}
 
-	if (lease -> filename)
-		client_envadd (client,
-			       prefix, "filename", "%s", lease -> filename);
-	if (lease -> server_name)
-		client_envadd (client, prefix, "server_name",
-			       "%s", lease -> server_name);
+	if (lease -> filename) {
+		if (check_option_values(NULL, DHO_ROOT_PATH,
+					lease -> filename,
+					strlen(lease -> filename)) == 0) {
+			client_envadd (client, prefix, "filename",
+			               "%s", lease -> filename);
+		} else {
+			log_error("suspect value in %s "
+			          "option - discarded",
+			          "filename");
+		}
+	}
+
+	if (lease -> server_name) {
+		if (check_option_values(NULL, DHO_HOST_NAME,
+					lease -> server_name,
+					strlen(lease -> server_name)) == 0) {
+			client_envadd (client, prefix, "server_name",
+				       "%s", lease -> server_name);
+		} else {
+			log_error("suspect value in %s "
+			          "option - discarded",
+			          "server-name");
+		}
+	}
 
 	for (i = 0; i < lease -> options -> universe_count; i++) {
 		option_space_foreach ((struct packet *)0, (struct lease *)0,
@@ -3256,3 +3293,103 @@ isc_result_t client_dns_update (struct c
 	data_string_forget (&ddns_dhcid, MDL);
 	return rcode;
 }
+
+static int check_domain_name(const char *ptr, size_t len, int dots)
+{
+	const char *p;
+
+	/* not empty or complete length not over 255 characters   */
+	if (len == 0 || len >= 256)
+		return -1;
+
+	/* consists of [[:alnum:]-]+ labels separated by [.]      */
+	/* a [_] is against RFC but seems to be "widely used"...  */
+	for (p=ptr; *p && len-- > 0; p++) {
+		if ( *p == '-' || *p == '_') {
+			/* not allowed at begin or end of a label */
+			if ((p - ptr) == 0 || len == 0 || p[1] == '.')
+				return -1;
+		} else
+		if ( *p == '.') {
+			/* each label has to be 1-63 characters;
+			   we allow [.] at the end ('foo.bar.')   */
+			ptrdiff_t d = p - ptr;
+			if( d <= 0 || d >= 64)
+				return -1;
+			ptr = p + 1; /* jump to the next label    */
+			if(dots > 0 && len > 0)
+				dots--;
+		} else
+		if ( !isalnum((unsigned char)*p)) {
+			/* also numbers at the begin are fine     */
+			return -1;
+		}
+	}
+	return dots ? -1 : 0;
+}
+
+static int check_domain_name_list(const char *ptr, size_t len, int dots)
+{
+	const char *p;
+	int ret = -1; /* at least one needed */
+
+	if (!ptr || !len)
+		return -1;
+
+	for (p=ptr; *p && len > 0; p++, len--) {
+		if (*p != ' ')
+			continue;
+		if (p > ptr) {
+			if (check_domain_name(ptr, p - ptr, dots) != 0)
+				return -1;
+			ret = 0;
+		}
+		ptr = p + 1;
+	}
+	if (p > ptr)
+		return check_domain_name(ptr, p - ptr, dots);
+	else
+		return ret;
+}
+
+static int check_option_values(	struct universe *universe,
+				unsigned int opt,
+				const char *ptr,
+				size_t len)
+{
+	if( !ptr)
+		return -1;
+
+	/* just reject options we want to protect, will be escaped anyway */
+	if ((universe == NULL) || (universe == &dhcp_universe)) {
+		switch(opt) {
+			case DHO_HOST_NAME:
+			case DHO_DOMAIN_NAME:
+			case DHO_NIS_DOMAIN:
+			case DHO_NETBIOS_SCOPE:
+				return check_domain_name(ptr, len, 0);
+			break;
+			case DHO_DOMAIN_SEARCH:
+				return check_domain_name_list(ptr, len, 0);
+			break;
+			case DHO_ROOT_PATH:
+				if( len == 0)
+					return -1;
+				for (; *ptr && len-- > 0; ptr++) {
+					if( !(isalnum((unsigned char)*ptr) ||
+						*ptr == '#'  || *ptr == '%' ||
+						*ptr == '+'  || *ptr == '-' ||
+						*ptr == '_'  || *ptr == ':' ||
+						*ptr == '.'  || *ptr == ',' ||
+						*ptr == '@'  || *ptr == '~' ||
+						*ptr == '\\' || *ptr == '/' ||
+						*ptr == ' '))
+						return -1;
+				}
+				return 0;
+			break;
+		}
+	}
+	return 0;
+}
+
Index: dhcp-3.1.1/common/options.c
===================================================================
--- dhcp-3.1.1.orig/common/options.c
+++ dhcp-3.1.1/common/options.c
@@ -2934,7 +2934,8 @@ pretty_escape(char **dst, char *dend, co
 				count += 4;
 			}
 		} else if (**src == '"' || **src == '\'' || **src == '$' ||
-			   **src == '`' || **src == '\\') {
+			   **src == '`' || **src == '\\' || **src == '|' ||
+			   **src == '&' || **src == ';') {
 			if (*dst + 2 > dend)
 				return -1;
 
Index: dhcp-3.1.1/client/scripts/linux
===================================================================
--- dhcp-3.1.1.orig/client/scripts/linux
+++ dhcp-3.1.1/client/scripts/linux
@@ -267,18 +267,29 @@ if [ x$reason = xBOUND ] || [ x$reason =
         /etc/sysconfig/network/ifcfg-${interface} 2>/dev/null`
   fi
   if [ "$DHCLIENT_SET_HOSTNAME" = yes ] ; then
+    rx_host='^[[:alnum:]][[:alnum:]_-]{0,62}$'
+
+    new_host_name="${new_host_name%%.*}"
+    [[ ${new_host_name} =~ ${rx_host} ]] || unset new_host_name
 
     current_hostname=`hostname`
-    if [ "x${current_hostname%%.*}" = x ] || \
-       [ "x${current_hostname%%.*}" != "x${new_host_name%%.*}" ]; then
 
-        if [ "x${new_host_name%%.*}" != x ]; then
-          hostname "${new_host_name%%.*}"
+    current_hostname="${current_hostname%%.*}"
+    [[ ${current_hostname} =~ ${rx_host} ]] || unset current_hostname
+
+    if [ "x${current_hostname}" = "x" ] || \
+       [ "x${current_hostname}" = "xlocalhost" ] || \
+       [ "x${current_hostname}" != "x${new_host_name}" ]; then
+
+        if [ "x${new_host_name}" != "x" ]; then
+	  hostname "${new_host_name}"
         else
           if [ -x /usr/bin/host ] ; then
             if out=`host -W 2 "$new_ip_address" 2>/dev/null` ; then
-              _hostname="`echo "$out" | sed 's:^.* ::; s:\..*::'`"
-              if [ "x${_hostname}" != x ]; then
+	      _hostname="`echo "$out" | sed 's:^.* ::; s:\..*::; s:.*[)]::'`"
+	      [[ ${_hostname} =~ ${rx_host} ]] || unset _hostname
+              if [ "x${_hostname}" != "x" -a \
+	           "x${_hostname}" != "x${current_hostname}" ]; then
                 hostname "${_hostname}"
               fi
             fi