aboutsummaryrefslogtreecommitdiffstats
path: root/main/unbound/CVE-2019-18934.patch
blob: 8d37b9b212be10bd085a8bf5ccbfee34dc01335d (plain)
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
diff --git a/ipsecmod/ipsecmod.c b/ipsecmod/ipsecmod.c
index c8400c6..9e916d6 100644
--- a/ipsecmod/ipsecmod.c
+++ b/ipsecmod/ipsecmod.c
@@ -161,6 +161,71 @@ generate_request(struct module_qstate* qstate, int id, uint8_t* name,
 	return 1;
 }
 
+/**
+ * Check if the string passed is a valid domain name with safe characters to
+ * pass to a shell.
+ * This will only allow:
+ *  - digits
+ *  - alphas
+ *  - hyphen (not at the start)
+ *  - dot (not at the start, or the only character)
+ *  - underscore
+ * @param s: pointer to the string.
+ * @param slen: string's length.
+ * @return true if s only contains safe characters; false otherwise.
+ */
+static int
+domainname_has_safe_characters(char* s, size_t slen) {
+	size_t i;
+	for(i = 0; i < slen; i++) {
+		if(s[i] == '\0') return 1;
+		if((s[i] == '-' && i != 0)
+			|| (s[i] == '.' && (i != 0 || s[1] == '\0'))
+			|| (s[i] == '_') || (s[i] >= '0' && s[i] <= '9')
+			|| (s[i] >= 'A' && s[i] <= 'Z')
+			|| (s[i] >= 'a' && s[i] <= 'z')) {
+			continue;
+		}
+		return 0;
+	}
+	return 1;
+}
+
+/**
+ * Check if the stringified IPSECKEY RDATA contains safe characters to pass to
+ * a shell.
+ * This is only relevant for checking the gateway when the gateway type is 3
+ * (domainname).
+ * @param s: pointer to the string.
+ * @param slen: string's length.
+ * @return true if s contains only safe characters; false otherwise.
+ */
+static int
+ipseckey_has_safe_characters(char* s, size_t slen) {
+	int precedence, gateway_type, algorithm;
+	char* gateway;
+	gateway = (char*)calloc(slen, sizeof(char));
+	if(!gateway) {
+		log_err("ipsecmod: out of memory when calling the hook");
+		return 0;
+	}
+	if(sscanf(s, "%d %d %d %s ",
+			&precedence, &gateway_type, &algorithm, gateway) != 4) {
+		free(gateway);
+		return 0;
+	}
+	if(gateway_type != 3) {
+		free(gateway);
+		return 1;
+	}
+	if(domainname_has_safe_characters(gateway, slen)) {
+		free(gateway);
+		return 1;
+	}
+	free(gateway);
+	return 0;
+}
+
 /**
  *  Prepare the data and call the hook.
  *
@@ -175,7 +240,7 @@ call_hook(struct module_qstate* qstate, struct ipsecmod_qstate* iq,
 {
 	size_t slen, tempdata_len, tempstring_len, i;
 	char str[65535], *s, *tempstring;
-	int w;
+	int w = 0, w_temp, qtype;
 	struct ub_packed_rrset_key* rrset_key;
 	struct packed_rrset_data* rrset_data;
 	uint8_t *tempdata;
@@ -192,9 +257,9 @@ call_hook(struct module_qstate* qstate, struct ipsecmod_qstate* iq,
 	memset(s, 0, slen);
 
 	/* Copy the hook into the buffer. */
-	sldns_str_print(&s, &slen, "%s", qstate->env->cfg->ipsecmod_hook);
+	w += sldns_str_print(&s, &slen, "%s", qstate->env->cfg->ipsecmod_hook);
 	/* Put space into the buffer. */
-	sldns_str_print(&s, &slen, " ");
+	w += sldns_str_print(&s, &slen, " ");
 	/* Copy the qname into the buffer. */
 	tempstring = sldns_wire2str_dname(qstate->qinfo.qname,
 		qstate->qinfo.qname_len);
@@ -202,68 +267,96 @@ call_hook(struct module_qstate* qstate, struct ipsecmod_qstate* iq,
 		log_err("ipsecmod: out of memory when calling the hook");
 		return 0;
 	}
-	sldns_str_print(&s, &slen, "\"%s\"", tempstring);
+	if(!domainname_has_safe_characters(tempstring, strlen(tempstring))) {
+		log_err("ipsecmod: qname has unsafe characters");
+		free(tempstring);
+		return 0;
+	}
+	w += sldns_str_print(&s, &slen, "\"%s\"", tempstring);
 	free(tempstring);
 	/* Put space into the buffer. */
-	sldns_str_print(&s, &slen, " ");
+	w += sldns_str_print(&s, &slen, " ");
 	/* Copy the IPSECKEY TTL into the buffer. */
 	rrset_data = (struct packed_rrset_data*)iq->ipseckey_rrset->entry.data;
-	sldns_str_print(&s, &slen, "\"%ld\"", (long)rrset_data->ttl);
+	w += sldns_str_print(&s, &slen, "\"%ld\"", (long)rrset_data->ttl);
 	/* Put space into the buffer. */
-	sldns_str_print(&s, &slen, " ");
-	/* Copy the A/AAAA record(s) into the buffer. Start and end this section
-	 * with a double quote. */
+	w += sldns_str_print(&s, &slen, " ");
 	rrset_key = reply_find_answer_rrset(&qstate->return_msg->qinfo,
 		qstate->return_msg->rep);
+	/* Double check that the records are indeed A/AAAA.
+	 * This should never happen as this function is only executed for A/AAAA
+	 * queries but make sure we don't pass anything other than A/AAAA to the
+	 * shell. */
+	qtype = ntohs(rrset_key->rk.type);
+	if(qtype != LDNS_RR_TYPE_AAAA && qtype != LDNS_RR_TYPE_A) {
+		log_err("ipsecmod: Answer is not of A or AAAA type");
+		return 0;
+	}
 	rrset_data = (struct packed_rrset_data*)rrset_key->entry.data;
-	sldns_str_print(&s, &slen, "\"");
+	/* Copy the A/AAAA record(s) into the buffer. Start and end this section
+	 * with a double quote. */
+	w += sldns_str_print(&s, &slen, "\"");
 	for(i=0; i<rrset_data->count; i++) {
 		if(i > 0) {
 			/* Put space into the buffer. */
-			sldns_str_print(&s, &slen, " ");
+			w += sldns_str_print(&s, &slen, " ");
 		}
 		/* Ignore the first two bytes, they are the rr_data len. */
-		w = sldns_wire2str_rdata_buf(rrset_data->rr_data[i] + 2,
+		w_temp = sldns_wire2str_rdata_buf(rrset_data->rr_data[i] + 2,
 			rrset_data->rr_len[i] - 2, s, slen, qstate->qinfo.qtype);
-		if(w < 0) {
+		if(w_temp < 0) {
 			/* Error in printout. */
-			return -1;
-		} else if((size_t)w >= slen) {
+			log_err("ipsecmod: Error in printing IP address");
+			return 0;
+		} else if((size_t)w_temp >= slen) {
 			s = NULL; /* We do not want str to point outside of buffer. */
 			slen = 0;
-			return -1;
+			log_err("ipsecmod: shell command too long");
+			return 0;
 		} else {
-			s += w;
-			slen -= w;
+			s += w_temp;
+			slen -= w_temp;
+			w += w_temp;
 		}
 	}
-	sldns_str_print(&s, &slen, "\"");
+	w += sldns_str_print(&s, &slen, "\"");
 	/* Put space into the buffer. */
-	sldns_str_print(&s, &slen, " ");
+	w += sldns_str_print(&s, &slen, " ");
 	/* Copy the IPSECKEY record(s) into the buffer. Start and end this section
 	 * with a double quote. */
-	sldns_str_print(&s, &slen, "\"");
+	w += sldns_str_print(&s, &slen, "\"");
 	rrset_data = (struct packed_rrset_data*)iq->ipseckey_rrset->entry.data;
 	for(i=0; i<rrset_data->count; i++) {
 		if(i > 0) {
 			/* Put space into the buffer. */
-			sldns_str_print(&s, &slen, " ");
+			w += sldns_str_print(&s, &slen, " ");
 		}
 		/* Ignore the first two bytes, they are the rr_data len. */
 		tempdata = rrset_data->rr_data[i] + 2;
 		tempdata_len = rrset_data->rr_len[i] - 2;
 		/* Save the buffer pointers. */
 		tempstring = s; tempstring_len = slen;
-		w = sldns_wire2str_ipseckey_scan(&tempdata, &tempdata_len, &s, &slen,
-			NULL, 0);
+		w_temp = sldns_wire2str_ipseckey_scan(&tempdata, &tempdata_len, &s,
+			&slen, NULL, 0);
 		/* There was an error when parsing the IPSECKEY; reset the buffer
 		 * pointers to their previous values. */
-		if(w == -1){
+		if(w_temp == -1) {
 			s = tempstring; slen = tempstring_len;
+		} else if(w_temp > 0) {
+			if(!ipseckey_has_safe_characters(
+					tempstring, tempstring_len - slen)) {
+				log_err("ipsecmod: ipseckey has unsafe characters");
+				return 0;
+			}
+			w += w_temp;
 		}
 	}
-	sldns_str_print(&s, &slen, "\"");
-	verbose(VERB_ALGO, "ipsecmod: hook command: '%s'", str);
+	w += sldns_str_print(&s, &slen, "\"");
+	if(w >= (int)sizeof(str)) {
+		log_err("ipsecmod: shell command too long");
+		return 0;
+	}
+	verbose(VERB_ALGO, "ipsecmod: shell command: '%s'", str);
 	/* ipsecmod-hook should return 0 on success. */
 	if(system(str) != 0)
 		return 0;