Package: dhcpcd Version: 1:3.2.3-9 Severity: normal Tags: patch User: ubuntu-de...@lists.ubuntu.com Usertags: origin-ubuntu precise ubuntu-patch
*** /tmp/tmpYPCJL7/bug_body In Ubuntu, the attached patch was applied to achieve the following: * SECURITY UPDATE: dhcpcd before 5.2.12 allows remote attackers to execute arbitrary commands via shell metacharacters in a hostname obtained from a DHCP message. (LP: #931036) - https://build.opensuse.org/package/view_file?file=dhcpcd-3.2.3-option-checks.diff&package=dhcpcd&project=network%3Adhcp&rev=52442e5c1d803d7c1818a920a0bae7f1 - above linked patch(without the additional support for NETBIOS type messages) has been added. - CVE-2011-0996 Thanks for considering the patch. -- System Information: Debian Release: wheezy/sid APT prefers precise-updates APT policy: (500, 'precise-updates'), (500, 'precise-security'), (500, 'precise-proposed'), (500, 'precise') Architecture: amd64 (x86_64) Kernel: Linux 3.2.0-15-generic (SMP w/4 CPU cores) Locale: LANG=en_CA.UTF-8, LC_CTYPE=en_CA.UTF-8 (charmap=UTF-8) Shell: /bin/sh linked to /bin/dash
diff -u dhcpcd-3.2.3/dhcpcd.c dhcpcd-3.2.3/dhcpcd.c --- dhcpcd-3.2.3/dhcpcd.c +++ dhcpcd-3.2.3/dhcpcd.c @@ -180,8 +180,10 @@ options->timeout = DEFAULT_TIMEOUT; snprintf(options->pidfile, sizeof (options->pidfile), "none"); - gethostname (options->hostname, sizeof (options->hostname)); - if (strcmp (options->hostname, "(none)") == 0 || + memset (options->hostname, 0, sizeof (options->hostname)); + gethostname (options->hostname, sizeof (options->hostname) - 1); + options->hostname[sizeof (options->hostname) - 1] = '\0'; + if (check_domain_name(options->hostname, strlen(options->hostname), 0) != 0 || strcmp (options->hostname, "localhost") == 0) memset (options->hostname, 0, sizeof (options->hostname)); @@ -230,6 +232,9 @@ "`%s' too long for HostName string, max is %d", optarg, MAXHOSTNAMELEN); goto abort; + } else if(check_domain_name(optarg, strlen(optarg), 0) != 0) { + logger (LOG_ERR, "suspect string in hostname argument"); + goto abort; } else strlcpy (options->hostname, optarg, sizeof (options->hostname)); diff -u dhcpcd-3.2.3/dhcp.c dhcpcd-3.2.3/dhcp.c --- dhcpcd-3.2.3/dhcp.c +++ dhcpcd-3.2.3/dhcp.c @@ -41,6 +41,8 @@ #include <stdint.h> #include <stdlib.h> #include <string.h> +#include <stddef.h> +#include <ctype.h> #include "config.h" @@ -627,6 +629,98 @@ return (head); } +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; +} + +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; +} + +int check_dhcp_option(unsigned char option, const char *ptr, size_t len) +{ + if( !ptr) + return -1; + + switch (option) { + case DHCP_HOSTNAME: + case DHCP_NISDOMAIN: + case DHCP_SIPSERVER: + case DHCP_DNSDOMAIN: /* accept a list for compatibiliy */ + case DHCP_DNSSEARCH: + return check_domain_name_list(ptr, len, 0); + break; + case DHCP_ROOTPATH: + 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 == '[' || *ptr == ']' || + *ptr == '=' || *ptr == ' ')) + return -1; + } + return 0; + break; + } + return 0; +} + static struct route_head *decode_routers (const unsigned char *data, int length) { int i; @@ -674,8 +768,16 @@ dhcp->leasedfrom = tv.tv_sec; dhcp->frominfo = false; dhcp->address.s_addr = message->yiaddr; - strlcpy (dhcp->servername, (char *) message->servername, - sizeof (dhcp->servername)); + if (message->servername[0] != '\0' && + check_domain_name((const char *)message->servername, + strlen((const char *)message->servername), 0) != 0) + { + logger (LOG_ERR, "suspect value in SERVERNAME - discarded"); + dhcp->servername[0] = '\0'; + } else { + strlcpy (dhcp->servername, (char *) message->servername, + sizeof (dhcp->servername)); + } #define LEN_ERR \ { \ @@ -789,6 +891,13 @@ #undef GET_UINT16 #undef GET_UINT8 +#define CHECKOPT(_opt,_var) \ + if(check_dhcp_option(_opt, (const char *)p, length) != 0) { \ + logger (LOG_ERR, "suspect value in option %s - discarded", #_opt); \ + if (_var) free (_var); \ + _var = NULL; \ + } + #define GETSTR(_var) { \ MIN_LENGTH (sizeof (char)); \ if (_var) free (_var); \ @@ -797,9 +906,13 @@ memset (_var + length, 0, 1); \ } case DHCP_HOSTNAME: + CHECKOPT (DHCP_HOSTNAME, dhcp->hostname) + else GETSTR (dhcp->hostname); break; case DHCP_DNSDOMAIN: + CHECKOPT (DHCP_DNSDOMAIN, dhcp->dnsdomain) + else GETSTR (dhcp->dnsdomain); break; case DHCP_MESSAGE: @@ -807,11 +920,15 @@ break; #ifdef ENABLE_INFO case DHCP_ROOTPATH: + CHECKOPT (DHCP_ROOTPATH, dhcp->rootpath) + else GETSTR (dhcp->rootpath); break; #endif #ifdef ENABLE_NIS case DHCP_NISDOMAIN: + CHECKOPT (DHCP_NISDOMAIN, dhcp->nisdomain) + else GETSTR (dhcp->nisdomain); break; #endif @@ -842,11 +959,21 @@ case DHCP_DNSSEARCH: MIN_LENGTH (1); free (dhcp->dnssearch); + dhcp->dnssearch = NULL; len = decode_search (p, length, NULL); if (len > 0) { - dhcp->dnssearch = xmalloc (len); - decode_search (p, length, - dhcp->dnssearch); + char *str = xmalloc (len); + decode_search (p, length, str); + if (check_dhcp_option(DHCP_DNSSEARCH, + str, len - 1) != 0) { + logger (LOG_ERR, + "suspect value in " + "option %s - discarded", + "DHCP_DNSSEARCH"); + free(str); + } else { + dhcp->dnssearch = str; + } } break; @@ -864,8 +991,22 @@ #ifdef ENABLE_INFO case DHCP_SIPSERVER: - free (dhcp->sipservers); - dhcp->sipservers = decode_sipservers (p,length); + if(dhcp->sipservers) + free (dhcp->sipservers); + dhcp->sipservers = NULL; + { + char *str = decode_sipservers (p,length); + if(str && check_dhcp_option(DHCP_SIPSERVER, + str, strlen(str)) != 0) { + logger (LOG_ERR, + "suspect value in " + "option %s - discarded", + "DHCP_SIPSERVER"); + free(str); + } else { + dhcp->sipservers = str; + } + } break; #endif @@ -901,6 +1042,7 @@ #undef LENGTH #undef MIN_LENGTH #undef MULT_LENGTH +#undef CHECKOPT default: logger (LOG_DEBUG, diff -u dhcpcd-3.2.3/debian/changelog dhcpcd-3.2.3/debian/changelog only in patch2: unchanged: --- dhcpcd-3.2.3.orig/configure.c +++ dhcpcd-3.2.3/configure.c @@ -453,7 +453,7 @@ char *addr; struct addrinfo hints; struct addrinfo *res = NULL; - int result; + int result, check; char *p; logger (LOG_DEBUG, "Looking up hostname via DNS"); @@ -479,9 +479,10 @@ result = getaddrinfo (addr, "0", &hints, &res); if (res) freeaddrinfo (res); - if (result == 0) + check = check_domain_name(addr, strlen(addr), 0); + if (result == 0 || check != 0) logger (LOG_ERR, "malicious PTR record detected"); - if (result == 0 || ! *addr) { + if (result == 0 || ! *addr || check != 0) { free (addr); return (NULL); } @@ -758,12 +759,12 @@ #endif curhostname = xmalloc (sizeof (char) * MAXHOSTNAMELEN); - *curhostname = '\0'; + memset(curhostname, 0, MAXHOSTNAMELEN); - gethostname (curhostname, MAXHOSTNAMELEN); + gethostname (curhostname, MAXHOSTNAMELEN - 1); + curhostname[MAXHOSTNAMELEN - 1] = '\0'; if (options->dohostname || - strlen (curhostname) == 0 || - strcmp (curhostname, "(none)") == 0 || + check_domain_name(curhostname, strlen (curhostname), 0) != 0 || strcmp (curhostname, "localhost") == 0) { newhostname = xmalloc (sizeof (char) * MAXHOSTNAMELEN); only in patch2: unchanged: --- dhcpcd-3.2.3.orig/dhcp.h +++ dhcpcd-3.2.3/dhcp.h @@ -212,5 +212,9 @@ uint32_t xid, char type, const options_t *options); void free_dhcp (dhcp_t *dhcp); int parse_dhcpmessage (dhcp_t *dhcp, const dhcpmessage_t *message); +int check_dhcp_option(unsigned char option, const char *ptr, size_t len); +int check_domain_name(const char *ptr, size_t len, int dots); +int check_domain_name_list(const char *ptr, size_t len, int dots); + #endif