1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
From a75807d231c66cf631919736b8694294e93fb18f Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 8 Dec 2011 16:41:05 +0100
Subject: [PATCH] udhcpc: sanitize hostnames in incoming packets. Closes 3979.
The following options are replaced with string "bad" if they
contain malformed hostname:
HOST_NAME, DOMAIN_NAME, NIS_DOMAIN, TFTP_SERVER_NAME
function old new delta
xmalloc_optname_optval 850 888 +38
attach_option 440 443 +3
len_of_option_as_string 13 14 +1
dhcp_option_lengths 13 14 +1
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 4/0 up/down: 43/0) Total: 43 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Conflicts:
networking/udhcp/dhcpc.c
(cherry picked from commit cd9072e71e5e86c34a6d71c44dde0cafb988d8f1)
Conflicts:
networking/udhcp/dhcpc.c
(cherry picked from commit 6904feafd1a57bee794a10a928132aaa77855101)
Conflicts:
networking/udhcp/common.c
---
networking/udhcp/common.c | 14 +++++++----
networking/udhcp/common.h | 3 +++
networking/udhcp/dhcpc.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 73 insertions(+), 6 deletions(-)
diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c
index 90a07ed..6001e84 100644
--- a/networking/udhcp/common.c
+++ b/networking/udhcp/common.c
@@ -29,15 +29,15 @@ const struct dhcp_optflag dhcp_optflags[] = {
// { OPTION_IP | OPTION_LIST , 0x07 }, /* DHCP_LOG_SERVER */
// { OPTION_IP | OPTION_LIST , 0x08 }, /* DHCP_COOKIE_SERVER */
{ OPTION_IP | OPTION_LIST , 0x09 }, /* DHCP_LPR_SERVER */
- { OPTION_STRING | OPTION_REQ, 0x0c }, /* DHCP_HOST_NAME */
+ { OPTION_STRING_HOST | OPTION_REQ, 0x0c }, /* DHCP_HOST_NAME */
{ OPTION_U16 , 0x0d }, /* DHCP_BOOT_SIZE */
- { OPTION_STRING | OPTION_REQ, 0x0f }, /* DHCP_DOMAIN_NAME */
+ { OPTION_STRING_HOST | OPTION_REQ, 0x0f }, /* DHCP_DOMAIN_NAME */
{ OPTION_IP , 0x10 }, /* DHCP_SWAP_SERVER */
{ OPTION_STRING , 0x11 }, /* DHCP_ROOT_PATH */
{ OPTION_U8 , 0x17 }, /* DHCP_IP_TTL */
{ OPTION_U16 , 0x1a }, /* DHCP_MTU */
{ OPTION_IP | OPTION_REQ, 0x1c }, /* DHCP_BROADCAST */
- { OPTION_STRING , 0x28 }, /* DHCP_NIS_DOMAIN */
+ { OPTION_STRING_HOST , 0x28 }, /* DHCP_NIS_DOMAIN */
{ OPTION_IP | OPTION_LIST , 0x29 }, /* DHCP_NIS_SERVER */
{ OPTION_IP | OPTION_LIST | OPTION_REQ, 0x2a }, /* DHCP_NTP_SERVER */
{ OPTION_IP | OPTION_LIST , 0x2c }, /* DHCP_WINS_SERVER */
@@ -45,7 +45,7 @@ const struct dhcp_optflag dhcp_optflags[] = {
{ OPTION_IP , 0x36 }, /* DHCP_SERVER_ID */
{ OPTION_STRING , 0x38 }, /* DHCP_ERR_MESSAGE */
//TODO: must be combined with 'sname' and 'file' handling:
- { OPTION_STRING , 0x42 }, /* DHCP_TFTP_SERVER_NAME */
+ { OPTION_STRING_HOST , 0x42 }, /* DHCP_TFTP_SERVER_NAME */
{ OPTION_STRING , 0x43 }, /* DHCP_BOOT_FILE */
//TODO: not a string, but a set of LASCII strings:
// { OPTION_STRING , 0x4D }, /* DHCP_USER_CLASS */
@@ -130,6 +130,7 @@ const uint8_t dhcp_option_lengths[] ALIGN1 = {
[OPTION_IP_PAIR] = 8,
// [OPTION_BOOLEAN] = 1,
[OPTION_STRING] = 1, /* ignored by udhcp_str2optset */
+ [OPTION_STRING_HOST] = 1, /* ignored by udhcp_str2optset */
#if ENABLE_FEATURE_UDHCP_RFC3397
[OPTION_DNS_STRING] = 1, /* ignored by both udhcp_str2optset and xmalloc_optname_optval */
[OPTION_SIP_SERVERS] = 1,
@@ -398,7 +399,9 @@ static NOINLINE void attach_option(
/* actually 255 is ok too, but adding a space can overlow it */
existing->data = xrealloc(existing->data, OPT_DATA + 1 + old_len + length);
- if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_STRING) {
+ if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_STRING
+ || (optflag->flags & OPTION_TYPE_MASK) == OPTION_STRING_HOST
+ ) {
/* add space separator between STRING options in a list */
existing->data[OPT_DATA + old_len] = ' ';
old_len++;
@@ -462,6 +465,7 @@ int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg)
retval = udhcp_str2nip(val, buffer + 4);
break;
case OPTION_STRING:
+ case OPTION_STRING_HOST:
#if ENABLE_FEATURE_UDHCP_RFC3397
case OPTION_DNS_STRING:
#endif
diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h
index ce81d18..5aed1e6 100644
--- a/networking/udhcp/common.h
+++ b/networking/udhcp/common.h
@@ -80,6 +80,9 @@ enum {
OPTION_IP = 1,
OPTION_IP_PAIR,
OPTION_STRING,
+ /* Opts of STRING_HOST type will be sanitized before they are passed
+ * to udhcpc script's environment: */
+ OPTION_STRING_HOST,
// OPTION_BOOLEAN,
OPTION_U8,
OPTION_U16,
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
index de1b798..50deea8 100644
--- a/networking/udhcp/dhcpc.c
+++ b/networking/udhcp/dhcpc.c
@@ -80,6 +80,63 @@ static int mton(uint32_t mask)
return i;
}
+/* Check if a given label represents a valid DNS label
+ * Return pointer to the first character after the label upon success,
+ * NULL otherwise.
+ * See RFC1035, 2.3.1
+ */
+/* We don't need to be particularly anal. For example, allowing _, hyphen
+ * at the end, or leading and trailing dots would be ok, since it
+ * can't be used for attacks. (Leading hyphen can be, if someone uses
+ * cmd "$hostname"
+ * in the script: then hostname may be treated as an option)
+ */
+static const char *valid_domain_label(const char *label)
+{
+ unsigned char ch;
+ unsigned pos = 0;
+
+ for (;;) {
+ ch = *label;
+ if ((ch|0x20) < 'a' || (ch|0x20) > 'z') {
+ if (pos == 0) {
+ /* label must begin with letter */
+ return NULL;
+ }
+ if (ch < '0' || ch > '9') {
+ if (ch == '\0' || ch == '.')
+ return label;
+ /* DNS allows only '-', but we are more permissive */
+ if (ch != '-' && ch != '_')
+ return NULL;
+ }
+ }
+ label++;
+ pos++;
+ //Do we want this?
+ //if (pos > 63) /* NS_MAXLABEL; labels must be 63 chars or less */
+ // return NULL;
+ }
+}
+
+/* Check if a given name represents a valid DNS name */
+/* See RFC1035, 2.3.1 */
+static int good_hostname(const char *name)
+{
+ //const char *start = name;
+
+ for (;;) {
+ name = valid_domain_label(name);
+ if (!name)
+ return 0;
+ if (!name[0])
+ return 1;
+ //Do we want this?
+ //return ((name - start) < 1025); /* NS_MAXDNAME */
+ name++;
+ }
+}
+
/* Create "opt_name=opt_value" string */
static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_optflag *optflag, const char *opt_name)
{
@@ -130,9 +187,12 @@ static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_
break;
}
case OPTION_STRING:
+ case OPTION_STRING_HOST:
memcpy(dest, option, len);
dest[len] = '\0';
- return ret; /* Short circuit this case */
+ if (type == OPTION_STRING_HOST && !good_hostname(dest))
+ safe_strncpy(dest, "bad", len);
+ return ret;
case OPTION_STATIC_ROUTES: {
/* Option binary format:
* mask [one byte, 0..32]
--
1.7.11.4
|