From ddf9daf8e4dfc2d4273f638a95fba2220a4ef031 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Kain" Date: Wed, 30 Mar 2011 15:57:01 -0400 Subject: [PATCH] Do more boundary and sanity checking when adding dhcp options. Simplify parameter list for ifchd_cmd(). --- ndhc/dhcpmsg.c | 39 ++++++++------ ndhc/ifchange.c | 14 +++--- ndhc/options.c | 131 +++++++++++++++++++++++++++--------------------- ndhc/options.h | 32 +++++++++--- ndhc/packet.c | 18 +++++-- 5 files changed, 143 insertions(+), 91 deletions(-) diff --git a/ndhc/dhcpmsg.c b/ndhc/dhcpmsg.c index c6ce6d7..cb3bbd6 100644 --- a/ndhc/dhcpmsg.c +++ b/ndhc/dhcpmsg.c @@ -79,7 +79,8 @@ static void init_header(struct dhcpMessage *packet, char type) packet->hlen = 6; // ETH_10MB_LEN packet->cookie = htonl(DHCP_MAGIC); packet->options[0] = DHCP_END; - add_simple_option(packet->options, DHCP_MESSAGE_TYPE, type); + add_u32_option(packet->options, DHCP_OPTIONS_BUFSIZE, DHCP_MESSAGE_TYPE, + type); } /* initialize a packet with the proper defaults */ @@ -93,10 +94,13 @@ static void init_packet(struct dhcpMessage *packet, char type) init_header(packet, type); memcpy(packet->chaddr, client_config.arp, 6); - add_option_string(packet->options, client_config.clientid); + add_option_string(packet->options, DHCP_OPTIONS_BUFSIZE, + client_config.clientid); if (client_config.hostname) - add_option_string(packet->options, client_config.hostname); - add_option_string(packet->options, (unsigned char *)&vendor_id); + add_option_string(packet->options, DHCP_OPTIONS_BUFSIZE, + client_config.hostname); + add_option_string(packet->options, DHCP_OPTIONS_BUFSIZE, + (unsigned char *)&vendor_id); } #define MAC_BCAST_ADDR (unsigned char *) "\xff\xff\xff\xff\xff\xff" @@ -117,11 +121,13 @@ int send_discover(uint32_t xid, uint32_t requested) init_packet(&packet, DHCPDISCOVER); packet.xid = xid; if (requested) - add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); + add_u32_option(packet.options, DHCP_OPTIONS_BUFSIZE, DHCP_REQUESTED_IP, + requested); /* Request a RFC-specified max size to work around buggy servers. */ - add_simple_option(packet.options, DHCP_MAX_SIZE, htons(576)); - add_requests(&packet); + add_u32_option(packet.options, DHCP_OPTIONS_BUFSIZE, + DHCP_MAX_SIZE, htons(576)); + add_option_request_list(&packet); log_line("Sending discover..."); return bcast_raw_packet(&packet); } @@ -135,10 +141,11 @@ int send_selecting(uint32_t xid, uint32_t server, uint32_t requested) init_packet(&packet, DHCPREQUEST); packet.xid = xid; - add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); - add_simple_option(packet.options, DHCP_SERVER_ID, server); + add_u32_option(packet.options, DHCP_OPTIONS_BUFSIZE, DHCP_REQUESTED_IP, + requested); + add_u32_option(packet.options, DHCP_OPTIONS_BUFSIZE, DHCP_SERVER_ID, server); - add_requests(&packet); + add_option_request_list(&packet); addr.s_addr = requested; log_line("Sending select for %s...", inet_ntoa(addr)); return bcast_raw_packet(&packet); @@ -153,7 +160,7 @@ int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr) packet.xid = xid; packet.ciaddr = ciaddr; - add_requests(&packet); + add_option_request_list(&packet); log_line("Sending renew..."); if (server) return kernel_packet(&packet, ciaddr, DHCP_CLIENT_PORT, server, @@ -179,8 +186,9 @@ int send_decline(uint32_t xid, uint32_t server, uint32_t requested) */ packet.xid = xid; /* DHCPDECLINE uses "requested ip", not ciaddr, to store offered IP */ - add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); - add_simple_option(packet.options, DHCP_SERVER_ID, server); + add_u32_option(packet.options, DHCP_OPTIONS_BUFSIZE, DHCP_REQUESTED_IP, + requested); + add_u32_option(packet.options, DHCP_OPTIONS_BUFSIZE, DHCP_SERVER_ID, server); log_line("Sending decline..."); return bcast_raw_packet(&packet); @@ -195,8 +203,9 @@ int send_release(uint32_t server, uint32_t ciaddr) packet.xid = random_xid(); packet.ciaddr = ciaddr; - add_simple_option(packet.options, DHCP_REQUESTED_IP, ciaddr); - add_simple_option(packet.options, DHCP_SERVER_ID, server); + add_u32_option(packet.options, DHCP_OPTIONS_BUFSIZE, DHCP_REQUESTED_IP, + ciaddr); + add_u32_option(packet.options, DHCP_OPTIONS_BUFSIZE, DHCP_SERVER_ID, server); log_line("Sending release..."); return kernel_packet(&packet, ciaddr, DHCP_CLIENT_PORT, server, diff --git a/ndhc/ifchange.c b/ndhc/ifchange.c index 9d25dc7..2329d4a 100644 --- a/ndhc/ifchange.c +++ b/ndhc/ifchange.c @@ -1,5 +1,5 @@ /* ifchange.c - functions to call the interface change daemon - * Time-stamp: <2011-03-30 12:02:55 nk> + * Time-stamp: <2011-03-30 13:14:37 nk> * * (c) 2004-2011 Nicholas J. Kain * @@ -40,13 +40,14 @@ /* Fill buf with the ifchd command text of option 'option'. */ /* Returns 0 if successful, -1 if nothing was filled in. */ -static int ifchd_cmd(char *buf, size_t buflen, - unsigned char *option, ssize_t optlen, - uint8_t code, enum option_type type, const char *optname) +static int ifchd_cmd(char *buf, size_t buflen, uint8_t *option, ssize_t optlen, + uint8_t code) { char *obuf = buf; uint8_t *ooption = option; - ssize_t typelen = option_length(type); + const char *optname = option_name(code); + enum option_type type = option_type(code); + ssize_t typelen = option_length(code); if (!option || type == OPTION_NONE) return -1; @@ -181,8 +182,7 @@ static void send_cmd(int sockfd, struct dhcpMessage *packet, memset(buf, '\0', sizeof buf); optdata = get_option_data(packet, code, &optlen); - if (ifchd_cmd(buf, sizeof buf, optdata, optlen, code, option_type(code), - option_name(code)) == -1) + if (ifchd_cmd(buf, sizeof buf, optdata, optlen, code) == -1) return; sockwrite(sockfd, buf, strlen(buf)); } diff --git a/ndhc/options.c b/ndhc/options.c index 31d9f31..84a02a4 100644 --- a/ndhc/options.c +++ b/ndhc/options.c @@ -1,7 +1,21 @@ -/* - * options.c -- DHCP server option packet tools - * Rewrite by Russ Dill July 2001 - * Fixes and hardening: Nicholas J. Kain +/* options.c - DHCP options handling + * Time-stamp: <2011-03-30 15:52:12 nk> + * + * (c) 2004-2011 Nicholas J. Kain + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include @@ -13,12 +27,6 @@ #include "log.h" #include "malloc.h" -enum { - OPT_CODE = 0, - OPT_LEN = 1, - OPT_DATA = 2 -}; - struct dhcp_option { char name[10]; enum option_type type; @@ -55,7 +63,7 @@ static struct dhcp_option options[] = { {"maxsize" , OPTION_U16, 0x39}, {"tftp" , OPTION_STRING, 0x42}, {"bootfile" , OPTION_STRING, 0x43}, - {"" , OPTION_NONE, 0x00} + {"NONE" , OPTION_NONE, 0x00} }; // List of options that will be sent on the parameter request list to the @@ -102,7 +110,7 @@ const char *option_name(uint8_t code) return bad_option_name; } -uint8_t option_length(enum option_type type) +static uint8_t option_type_length(enum option_type type) { switch (type) { case OPTION_IP: return 4; @@ -115,6 +123,16 @@ uint8_t option_length(enum option_type type) } } +uint8_t option_length(uint8_t code) +{ + int i; + for (i = 0; options[i].code; i++) + if (options[i].code == code) + return option_type_length(options[i].type); + log_warning("option_length: unknown length for code 0x%02x", code); + return 0; +} + int option_valid_list(uint8_t code) { int i; @@ -254,61 +272,60 @@ uint8_t *get_option_data(struct dhcpMessage *packet, int code, ssize_t *optlen) } /* return the position of the 'end' option */ -int get_end_option_idx(uint8_t *optbuf, size_t bufsize) +ssize_t get_end_option_idx(uint8_t *optbuf, size_t bufsize) { - int i = 0; - while (i < bufsize && optbuf[i] != DHCP_END) { + int i; + for (i = 0; i < bufsize; ++i) { + if (optbuf[i] == DHCP_END) + return i; + if (optbuf[i] == DHCP_PADDING) + continue; if (optbuf[i] != DHCP_PADDING) - i += optbuf[i+1] + 2; - else - i++; - } - if (i < bufsize - 1) - return i; - else { - log_warning("get_end_option_idx(): did not find DHCP_END marker"); - return bufsize - 1; + i += optbuf[i+1] + 1; } + log_warning("get_end_option_idx(): did not find DHCP_END marker"); + return -1; } - /* add an option string to the options (an option string contains an option * code, length, then data) */ -int add_option_string(unsigned char *optionptr, unsigned char *string) +size_t add_option_string(uint8_t *optbuf, size_t buflen, uint8_t *optstr) { - // XXX: not real length - int end = get_end_option_idx(optionptr, DHCP_OPTIONS_BUFSIZE); + size_t end = get_end_option_idx(optbuf, buflen); + size_t datalen = optstr[1]; - /* end position + string length + option code/length + end option */ - if (end + string[OPT_LEN] + 2 + 1 >= DHCP_OPTIONS_BUFSIZE) { - log_error("Option 0x%02x did not fit into the packet!", - string[OPT_CODE]); + if (end == -1) { + log_warning("add_option_string: Buffer has no DHCP_END marker"); return 0; } - memcpy(optionptr + end, string, string[OPT_LEN] + 2); - optionptr[end + string[OPT_LEN] + 2] = DHCP_END; - return string[OPT_LEN] + 2; + /* end position + optstr length + option code/length + end option */ + if (end + datalen + 2 + 1 >= buflen) { + log_warning("Option 0x%02x did not fit into the packet!", optstr[0]); + return 0; + } + memcpy(optbuf + end, optstr, datalen + 2); + optbuf[end + datalen + 2] = DHCP_END; + return datalen + 2; } -int add_simple_option(unsigned char *optionptr, unsigned char code, +size_t add_u32_option(uint8_t *optbuf, size_t buflen, uint8_t code, uint32_t data) { - int i, length = 0; - unsigned char option[2 + 4]; + int length = 0; + uint8_t option[6]; - for (i = 0; options[i].code; i++) - if (options[i].code == code) { - length = option_length(options[i].type); - } + length = option_length(code); - log_line("aso(): code=0x%02x length=0x%02x", code, length); - option[OPT_CODE] = code; - option[OPT_LEN] = (unsigned char)length; + log_line("add_u32_option: code=0x%02x length=0x%02x", code, length); + option[0] = code; + option[1] = length; if (!length) { - log_error("Could not add option 0x%02x", code); + log_warning("add_u32_option: option code 0x%02x has 0 length", code); return 0; - } else if (length == 1) { + } + + if (length == 1) { uint8_t t = (uint8_t)data; memcpy(option + 2, &t, 1); } else if (length == 2) { @@ -318,20 +335,18 @@ int add_simple_option(unsigned char *optionptr, unsigned char code, uint32_t t = (uint32_t)data; memcpy(option + 2, &t, 4); } - return add_option_string(optionptr, option); + return add_option_string(optbuf, buflen, option); } -/* Add a paramater request list for stubborn DHCP servers. Don't do bounds */ -/* checking here because it goes towards the head of the packet. */ -void add_requests(struct dhcpMessage *packet) +/* Add a paramater request list for stubborn DHCP servers */ +void add_option_request_list(struct dhcpMessage *packet) { - // XXX: not real length - int end = get_end_option_idx(packet->options, DHCP_OPTIONS_BUFSIZE); - int i, len = 0; + int i; + uint8_t reqdata[sizeof req_opts + 1]; - packet->options[end + OPT_CODE] = DHCP_PARAM_REQ; + reqdata[0] = DHCP_PARAM_REQ; + reqdata[1] = sizeof reqdata - 2; for (i = 0; req_opts[i]; i++) - packet->options[end + OPT_DATA + len++] = req_opts[i]; - packet->options[end + OPT_LEN] = len; - packet->options[end + OPT_DATA + len] = DHCP_END; + reqdata[i + 2] = req_opts[i]; + add_option_string(packet->options, DHCP_OPTIONS_BUFSIZE, reqdata); } diff --git a/ndhc/options.h b/ndhc/options.h index e749675..d306db0 100644 --- a/ndhc/options.h +++ b/ndhc/options.h @@ -1,4 +1,22 @@ -/* options.h */ +/* options.h - DHCP options handling + * Time-stamp: <2011-03-30 15:50:30 nk> + * + * (c) 2004-2011 Nicholas J. Kain + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ #ifndef OPTIONS_H_ #define OPTIONS_H_ @@ -64,7 +82,7 @@ enum option_type { const char *option_name(uint8_t code); enum option_type option_type(uint8_t code); -uint8_t option_length(enum option_type type); +uint8_t option_length(uint8_t code); int option_valid_list(uint8_t code); size_t sizeof_option(unsigned char code, size_t datalen); size_t set_option(unsigned char *buf, size_t buflen, unsigned char code, @@ -76,10 +94,10 @@ unsigned char *alloc_dhcp_client_id_option(unsigned char type, unsigned char *idstr, size_t idstrlen); uint8_t *get_option_data(struct dhcpMessage *packet, int code, ssize_t *optlen); -int get_end_option_idx(uint8_t *optbuf, size_t bufsize); -int add_option_string(unsigned char *optionptr, unsigned char *string); -int add_simple_option(unsigned char *optionptr, unsigned char code, uint32_t data); - -void add_requests(struct dhcpMessage *packet); +ssize_t get_end_option_idx(uint8_t *optbuf, size_t bufsize); +size_t add_option_string(uint8_t *optbuf, size_t buflen, uint8_t *optstr); +size_t add_u32_option(uint8_t *optbuf, size_t buflen, uint8_t code, + uint32_t data); +void add_option_request_list(struct dhcpMessage *packet); #endif diff --git a/ndhc/packet.c b/ndhc/packet.c index 5202d6b..10bbbde 100644 --- a/ndhc/packet.c +++ b/ndhc/packet.c @@ -105,8 +105,13 @@ int raw_packet(struct dhcpMessage *payload, uint32_t source_ip, * In order to work with those buggy servers, * we truncate packets after end option byte. */ - padding = DHCP_OPTIONS_BUFSIZE - 1 - get_end_option_idx(packet.data.options, - DHCP_OPTIONS_BUFSIZE); + ssize_t endloc = get_end_option_idx(packet.data.options, + DHCP_OPTIONS_BUFSIZE); + if (endloc == -1) { + log_error("raw_packet: attempt to send packet with no DHCP_END"); + goto out_fd; + } + padding = DHCP_OPTIONS_BUFSIZE - 1 - endloc; packet.ip.protocol = IPPROTO_UDP; packet.ip.saddr = source_ip; @@ -165,8 +170,13 @@ int kernel_packet(struct dhcpMessage *payload, uint32_t source_ip, if (connect(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1) goto out_fd; - padding = DHCP_OPTIONS_BUFSIZE - 1 - get_end_option_idx(payload->options, - DHCP_OPTIONS_BUFSIZE); + ssize_t endloc = get_end_option_idx(payload->options, + DHCP_OPTIONS_BUFSIZE); + if (endloc == -1) { + log_error("kernel_packet: attempt to send packet with no DHCP_END"); + goto out_fd; + } + padding = DHCP_OPTIONS_BUFSIZE - 1 - endloc; result = safe_write(fd, (const char *)payload, DHCP_SIZE - padding); if (result == -1) log_error("kernel_packet: write failed: %s", strerror(errno));