| | varnish-cache/bin/varnishd/http2/cache_http2_hpack.c |
0 |
|
/*- |
1 |
|
* Copyright (c) 2016 Varnish Software AS |
2 |
|
* All rights reserved. |
3 |
|
* |
4 |
|
* Author: Martin Blix Grydeland <martin@varnish-software.com> |
5 |
|
* |
6 |
|
* SPDX-License-Identifier: BSD-2-Clause |
7 |
|
* |
8 |
|
* Redistribution and use in source and binary forms, with or without |
9 |
|
* modification, are permitted provided that the following conditions |
10 |
|
* are met: |
11 |
|
* 1. Redistributions of source code must retain the above copyright |
12 |
|
* notice, this list of conditions and the following disclaimer. |
13 |
|
* 2. Redistributions in binary form must reproduce the above copyright |
14 |
|
* notice, this list of conditions and the following disclaimer in the |
15 |
|
* documentation and/or other materials provided with the distribution. |
16 |
|
* |
17 |
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
18 |
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
19 |
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
20 |
|
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE |
21 |
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
22 |
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
23 |
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
24 |
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
25 |
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
26 |
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
27 |
|
* SUCH DAMAGE. |
28 |
|
* |
29 |
|
*/ |
30 |
|
|
31 |
|
#include "config.h" |
32 |
|
|
33 |
|
#include "cache/cache_varnishd.h" |
34 |
|
|
35 |
|
#include <ctype.h> |
36 |
|
#include <stdio.h> |
37 |
|
|
38 |
|
#include "http2/cache_http2.h" |
39 |
|
#include "vct.h" |
40 |
|
|
41 |
|
// rfc9113,l,2493,2528 |
42 |
|
static h2_error |
43 |
11400 |
h2h_checkhdr(const struct http *hp, const char *b, size_t namelen, size_t len) |
44 |
|
{ |
45 |
|
const char *p; |
46 |
|
enum { |
47 |
|
FLD_NAME_FIRST, |
48 |
|
FLD_NAME, |
49 |
|
FLD_VALUE_FIRST, |
50 |
|
FLD_VALUE |
51 |
|
} state; |
52 |
|
|
53 |
11400 |
CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC); |
54 |
11400 |
AN(b); |
55 |
11400 |
assert(namelen >= 2); /* 2 chars from the ': ' that we added */ |
56 |
11400 |
assert(namelen <= len); |
57 |
11400 |
assert(b[namelen - 2] == ':'); |
58 |
11400 |
assert(b[namelen - 1] == ' '); |
59 |
|
|
60 |
11400 |
if (namelen == 2) { |
61 |
0 |
VSLb(hp->vsl, SLT_BogoHeader, "Empty name"); |
62 |
0 |
return (H2SE_PROTOCOL_ERROR); |
63 |
|
} |
64 |
|
|
65 |
|
// VSLb(hp->vsl, SLT_Debug, "CHDR [%.*s] [%.*s]", |
66 |
|
// (int)namelen, b, (int)(len - namelen), b + namelen); |
67 |
|
|
68 |
11400 |
state = FLD_NAME_FIRST; |
69 |
88975 |
for (p = b; p < b + namelen - 2; p++) { |
70 |
77675 |
switch(state) { |
71 |
|
case FLD_NAME_FIRST: |
72 |
11400 |
state = FLD_NAME; |
73 |
11400 |
if (*p == ':') |
74 |
9700 |
break; |
75 |
|
/* FALL_THROUGH */ |
76 |
|
case FLD_NAME: |
77 |
67975 |
if (isupper(*p)) { |
78 |
50 |
VSLb(hp->vsl, SLT_BogoHeader, |
79 |
|
"Illegal field header name (upper-case): %.*s", |
80 |
25 |
(int)(len > 20 ? 20 : len), b); |
81 |
25 |
return (H2SE_PROTOCOL_ERROR); |
82 |
|
} |
83 |
67950 |
if (!vct_istchar(*p) || *p == ':') { |
84 |
150 |
VSLb(hp->vsl, SLT_BogoHeader, |
85 |
|
"Illegal field header name (non-token): %.*s", |
86 |
75 |
(int)(len > 20 ? 20 : len), b); |
87 |
75 |
return (H2SE_PROTOCOL_ERROR); |
88 |
|
} |
89 |
67875 |
break; |
90 |
|
default: |
91 |
0 |
WRONG("http2 field name validation state"); |
92 |
0 |
} |
93 |
77575 |
} |
94 |
|
|
95 |
11300 |
state = FLD_VALUE_FIRST; |
96 |
56000 |
for (p = b + namelen; p < b + len; p++) { |
97 |
44950 |
switch(state) { |
98 |
|
case FLD_VALUE_FIRST: |
99 |
11125 |
if (vct_issp(*p)) { |
100 |
450 |
VSLb(hp->vsl, SLT_BogoHeader, |
101 |
|
"Illegal field value start %.*s", |
102 |
225 |
(int)(len > 20 ? 20 : len), b); |
103 |
225 |
return (H2SE_PROTOCOL_ERROR); |
104 |
|
} |
105 |
10900 |
state = FLD_VALUE; |
106 |
|
/* FALL_THROUGH */ |
107 |
|
case FLD_VALUE: |
108 |
44725 |
if (!vct_ishdrval(*p)) { |
109 |
50 |
VSLb(hp->vsl, SLT_BogoHeader, |
110 |
|
"Illegal field value %.*s", |
111 |
25 |
(int)(len > 20 ? 20 : len), b); |
112 |
25 |
return (H2SE_PROTOCOL_ERROR); |
113 |
|
} |
114 |
44700 |
break; |
115 |
|
default: |
116 |
0 |
WRONG("http2 field value validation state"); |
117 |
0 |
} |
118 |
44700 |
} |
119 |
11050 |
if (state == FLD_VALUE && vct_issp(b[len - 1])) { |
120 |
100 |
VSLb(hp->vsl, SLT_BogoHeader, |
121 |
|
"Illegal field value (end) %.*s", |
122 |
50 |
(int)(len > 20 ? 20 : len), b); |
123 |
50 |
return (H2SE_PROTOCOL_ERROR); |
124 |
|
} |
125 |
11000 |
return (0); |
126 |
11400 |
} |
127 |
|
|
128 |
|
static h2_error |
129 |
11000 |
h2h_addhdr(struct h2h_decode *d, struct http *hp, char *b, size_t namelen, |
130 |
|
size_t len) |
131 |
|
{ |
132 |
|
/* XXX: This might belong in cache/cache_http.c */ |
133 |
|
const char *b0; |
134 |
|
int disallow_empty; |
135 |
|
unsigned n; |
136 |
|
char *p; |
137 |
|
unsigned u; |
138 |
|
|
139 |
11000 |
CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC); |
140 |
11000 |
AN(b); |
141 |
11000 |
assert(namelen >= 2); /* 2 chars from the ': ' that we added */ |
142 |
11000 |
assert(namelen <= len); |
143 |
|
|
144 |
11000 |
disallow_empty = 0; |
145 |
|
|
146 |
11000 |
if (len > UINT_MAX) { /* XXX: cache_param max header size */ |
147 |
0 |
VSLb(hp->vsl, SLT_BogoHeader, "Header too large: %.20s", b); |
148 |
0 |
return (H2SE_ENHANCE_YOUR_CALM); |
149 |
|
} |
150 |
|
|
151 |
11000 |
b0 = b; |
152 |
11000 |
if (b[0] == ':') { |
153 |
|
/* Match H/2 pseudo headers */ |
154 |
|
/* XXX: Should probably have some include tbl for |
155 |
|
pseudo-headers */ |
156 |
9550 |
if (!strncmp(b, ":method: ", namelen)) { |
157 |
3000 |
b += namelen; |
158 |
3000 |
len -= namelen; |
159 |
3000 |
n = HTTP_HDR_METHOD; |
160 |
3000 |
disallow_empty = 1; |
161 |
|
|
162 |
|
/* First field cannot contain SP or CTL */ |
163 |
12750 |
for (p = b, u = 0; u < len; p++, u++) { |
164 |
9750 |
if (vct_issp(*p) || vct_isctl(*p)) |
165 |
0 |
return (H2SE_PROTOCOL_ERROR); |
166 |
9750 |
} |
167 |
9550 |
} else if (!strncmp(b, ":path: ", namelen)) { |
168 |
3100 |
b += namelen; |
169 |
3100 |
len -= namelen; |
170 |
3100 |
n = HTTP_HDR_URL; |
171 |
3100 |
disallow_empty = 1; |
172 |
|
|
173 |
|
// rfc9113,l,2693,2705 |
174 |
3100 |
if (len > 0 && *b != '/' && |
175 |
150 |
strncmp(b, "*", len) != 0) { |
176 |
100 |
VSLb(hp->vsl, SLT_BogoHeader, |
177 |
|
"Illegal :path pseudo-header %.*s", |
178 |
50 |
(int)len, b); |
179 |
50 |
return (H2SE_PROTOCOL_ERROR); |
180 |
|
} |
181 |
|
|
182 |
|
/* Second field cannot contain LWS or CTL */ |
183 |
9600 |
for (p = b, u = 0; u < len; p++, u++) { |
184 |
6550 |
if (vct_islws(*p) || vct_isctl(*p)) |
185 |
0 |
return (H2SE_PROTOCOL_ERROR); |
186 |
6550 |
} |
187 |
6500 |
} else if (!strncmp(b, ":scheme: ", namelen)) { |
188 |
|
/* XXX: What to do about this one? (typically |
189 |
|
"http" or "https"). For now set it as a normal |
190 |
|
header, stripping the first ':'. */ |
191 |
3025 |
if (d->has_scheme) { |
192 |
50 |
VSLb(hp->vsl, SLT_BogoHeader, |
193 |
|
"Duplicate pseudo-header %.*s%.*s", |
194 |
25 |
(int)namelen, b0, |
195 |
25 |
(int)(len > 20 ? 20 : len), b); |
196 |
25 |
return (H2SE_PROTOCOL_ERROR); |
197 |
|
} |
198 |
|
|
199 |
3000 |
b++; |
200 |
3000 |
len-=1; |
201 |
3000 |
n = hp->nhd; |
202 |
3000 |
d->has_scheme = 1; |
203 |
|
|
204 |
11850 |
for (p = b + namelen, u = 0; u < len-namelen; |
205 |
8850 |
p++, u++) { |
206 |
8900 |
if (vct_issp(*p) || vct_isctl(*p)) |
207 |
50 |
return (H2SE_PROTOCOL_ERROR); |
208 |
8850 |
} |
209 |
|
|
210 |
2950 |
if (!u) |
211 |
0 |
return (H2SE_PROTOCOL_ERROR); |
212 |
3375 |
} else if (!strncmp(b, ":authority: ", namelen)) { |
213 |
375 |
b+=6; |
214 |
375 |
len-=6; |
215 |
375 |
memcpy(b, "host", 4); |
216 |
375 |
n = hp->nhd; |
217 |
375 |
} else { |
218 |
|
/* Unknown pseudo-header */ |
219 |
100 |
VSLb(hp->vsl, SLT_BogoHeader, |
220 |
|
"Unknown pseudo-header: %.*s", |
221 |
50 |
(int)(len > 20 ? 20 : len), b); |
222 |
50 |
return (H2SE_PROTOCOL_ERROR); // rfc7540,l,2990,2992 |
223 |
|
} |
224 |
9375 |
} else |
225 |
1450 |
n = hp->nhd; |
226 |
|
|
227 |
10825 |
if (n < HTTP_HDR_FIRST) { |
228 |
|
/* Check for duplicate pseudo-header */ |
229 |
6050 |
if (hp->hd[n].b != NULL) { |
230 |
150 |
VSLb(hp->vsl, SLT_BogoHeader, |
231 |
|
"Duplicate pseudo-header %.*s%.*s", |
232 |
75 |
(int)namelen, b0, (int)(len > 20 ? 20 : len), b); |
233 |
75 |
return (H2SE_PROTOCOL_ERROR); // rfc7540,l,3158,3162 |
234 |
|
} |
235 |
5975 |
} else { |
236 |
|
/* Check for space in struct http */ |
237 |
4775 |
if (n >= hp->shd) { |
238 |
0 |
VSLb(hp->vsl, SLT_LostHeader, "Too many headers: %.*s", |
239 |
0 |
(int)(len > 20 ? 20 : len), b); |
240 |
0 |
return (H2SE_ENHANCE_YOUR_CALM); |
241 |
|
} |
242 |
4775 |
hp->nhd++; |
243 |
|
} |
244 |
|
|
245 |
10750 |
hp->hd[n].b = b; |
246 |
10750 |
hp->hd[n].e = b + len; |
247 |
|
|
248 |
10750 |
if (disallow_empty && !Tlen(hp->hd[n])) { |
249 |
200 |
VSLb(hp->vsl, SLT_BogoHeader, |
250 |
|
"Empty pseudo-header %.*s", |
251 |
100 |
(int)namelen, b0); |
252 |
100 |
return (H2SE_PROTOCOL_ERROR); |
253 |
|
} |
254 |
|
|
255 |
10650 |
return (0); |
256 |
11000 |
} |
257 |
|
|
258 |
|
void |
259 |
3800 |
h2h_decode_init(const struct h2_sess *h2) |
260 |
|
{ |
261 |
|
struct h2h_decode *d; |
262 |
|
|
263 |
3800 |
CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC); |
264 |
3800 |
CHECK_OBJ_NOTNULL(h2->new_req, REQ_MAGIC); |
265 |
3800 |
CHECK_OBJ_NOTNULL(h2->new_req->http, HTTP_MAGIC); |
266 |
3800 |
AN(h2->decode); |
267 |
3800 |
d = h2->decode; |
268 |
3800 |
INIT_OBJ(d, H2H_DECODE_MAGIC); |
269 |
3800 |
VHD_Init(d->vhd); |
270 |
3800 |
d->out_l = WS_ReserveAll(h2->new_req->http->ws); |
271 |
|
/* |
272 |
|
* Can't do any work without any buffer |
273 |
|
* space. Require non-zero size. |
274 |
|
*/ |
275 |
3800 |
XXXAN(d->out_l); |
276 |
3800 |
d->out = WS_Reservation(h2->new_req->http->ws); |
277 |
3800 |
d->reset = d->out; |
278 |
3800 |
} |
279 |
|
|
280 |
|
/* Possible error returns: |
281 |
|
* |
282 |
|
* H2E_COMPRESSION_ERROR: Lost compression state due to incomplete header |
283 |
|
* block. This is a connection level error. |
284 |
|
* |
285 |
|
* H2E_ENHANCE_YOUR_CALM: Ran out of workspace or http header space. This |
286 |
|
* is a stream level error. |
287 |
|
*/ |
288 |
|
h2_error |
289 |
3800 |
h2h_decode_fini(const struct h2_sess *h2) |
290 |
|
{ |
291 |
|
h2_error ret; |
292 |
|
struct h2h_decode *d; |
293 |
|
|
294 |
3800 |
CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC); |
295 |
3800 |
d = h2->decode; |
296 |
3800 |
CHECK_OBJ_NOTNULL(h2->new_req, REQ_MAGIC); |
297 |
3800 |
CHECK_OBJ_NOTNULL(d, H2H_DECODE_MAGIC); |
298 |
3800 |
WS_ReleaseP(h2->new_req->http->ws, d->out); |
299 |
3800 |
if (d->vhd_ret != VHD_OK) { |
300 |
|
/* HPACK header block didn't finish at an instruction |
301 |
|
boundary */ |
302 |
1650 |
VSLb(h2->new_req->http->vsl, SLT_BogoHeader, |
303 |
825 |
"HPACK compression error/fini (%s)", VHD_Error(d->vhd_ret)); |
304 |
825 |
ret = H2CE_COMPRESSION_ERROR; |
305 |
3800 |
} else if (d->error == NULL && !d->has_scheme) { |
306 |
100 |
VSLb(h2->vsl, SLT_Debug, "Missing :scheme"); |
307 |
100 |
ret = H2SE_MISSING_SCHEME; //rfc7540,l,3087,3090 |
308 |
100 |
} else |
309 |
2875 |
ret = d->error; |
310 |
3800 |
FINI_OBJ(d); |
311 |
3800 |
return (ret); |
312 |
|
} |
313 |
|
|
314 |
|
/* Possible error returns: |
315 |
|
* |
316 |
|
* H2E_COMPRESSION_ERROR: Lost compression state due to invalid header |
317 |
|
* block. This is a connection level error. |
318 |
|
* |
319 |
|
* H2E_PROTOCOL_ERROR: Malformed header or duplicate pseudo-header. |
320 |
|
* Violation of field name/value charsets |
321 |
|
*/ |
322 |
|
h2_error |
323 |
3850 |
h2h_decode_bytes(struct h2_sess *h2, const uint8_t *in, size_t in_l) |
324 |
|
{ |
325 |
|
struct http *hp; |
326 |
|
struct h2h_decode *d; |
327 |
3850 |
size_t in_u = 0; |
328 |
|
const char *r, *e; |
329 |
|
|
330 |
3850 |
CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC); |
331 |
3850 |
CHECK_OBJ_NOTNULL(h2->new_req, REQ_MAGIC); |
332 |
3850 |
hp = h2->new_req->http; |
333 |
3850 |
CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC); |
334 |
3850 |
CHECK_OBJ_NOTNULL(hp->ws, WS_MAGIC); |
335 |
3850 |
r = WS_Reservation(hp->ws); |
336 |
3850 |
AN(r); |
337 |
3850 |
e = r + WS_ReservationSize(hp->ws); |
338 |
3850 |
d = h2->decode; |
339 |
3850 |
CHECK_OBJ_NOTNULL(d, H2H_DECODE_MAGIC); |
340 |
|
|
341 |
|
/* Only H2E_ENHANCE_YOUR_CALM indicates that we should continue |
342 |
|
processing. Other errors should have been returned and handled |
343 |
|
by the caller. */ |
344 |
3850 |
assert(d->error == 0 || d->error == H2SE_ENHANCE_YOUR_CALM); |
345 |
|
|
346 |
25925 |
while (1) { |
347 |
25925 |
AN(d->out); |
348 |
25925 |
assert(d->out_u <= d->out_l); |
349 |
51850 |
d->vhd_ret = VHD_Decode(d->vhd, h2->dectbl, in, in_l, &in_u, |
350 |
25925 |
d->out, d->out_l, &d->out_u); |
351 |
|
|
352 |
25925 |
if (d->vhd_ret < 0) { |
353 |
100 |
VSLb(hp->vsl, SLT_BogoHeader, |
354 |
|
"HPACK compression error (%s)", |
355 |
50 |
VHD_Error(d->vhd_ret)); |
356 |
50 |
d->error = H2CE_COMPRESSION_ERROR; |
357 |
50 |
break; |
358 |
25875 |
} else if (d->vhd_ret == VHD_OK || d->vhd_ret == VHD_MORE) { |
359 |
3050 |
assert(in_u == in_l); |
360 |
3050 |
break; |
361 |
|
} |
362 |
|
|
363 |
22825 |
if (d->error == H2SE_ENHANCE_YOUR_CALM) { |
364 |
0 |
d->out_u = 0; |
365 |
0 |
assert(d->out_u < d->out_l); |
366 |
0 |
continue; |
367 |
|
} |
368 |
|
|
369 |
22825 |
switch (d->vhd_ret) { |
370 |
|
case VHD_NAME_SEC: |
371 |
|
/* XXX: header flag for never-indexed header */ |
372 |
|
case VHD_NAME: |
373 |
11425 |
assert(d->namelen == 0); |
374 |
11425 |
if (d->out_l - d->out_u < 2) { |
375 |
0 |
d->error = H2SE_ENHANCE_YOUR_CALM; |
376 |
0 |
break; |
377 |
|
} |
378 |
11425 |
d->out[d->out_u++] = ':'; |
379 |
11425 |
d->out[d->out_u++] = ' '; |
380 |
11425 |
d->namelen = d->out_u; |
381 |
11425 |
break; |
382 |
|
|
383 |
|
case VHD_VALUE_SEC: |
384 |
|
/* XXX: header flag for never-indexed header */ |
385 |
|
case VHD_VALUE: |
386 |
11400 |
assert(d->namelen > 0); |
387 |
11400 |
if (d->out_l - d->out_u < 1) { |
388 |
0 |
d->error = H2SE_ENHANCE_YOUR_CALM; |
389 |
0 |
break; |
390 |
|
} |
391 |
22800 |
d->error = h2h_checkhdr(hp, d->out, d->namelen, |
392 |
11400 |
d->out_u); |
393 |
11400 |
if (d->error) |
394 |
400 |
break; |
395 |
22000 |
d->error = h2h_addhdr(d, hp, d->out, |
396 |
11000 |
d->namelen, d->out_u); |
397 |
11000 |
if (d->error) |
398 |
350 |
break; |
399 |
10650 |
d->out[d->out_u++] = '\0'; /* Zero guard */ |
400 |
10650 |
d->out += d->out_u; |
401 |
10650 |
d->out_l -= d->out_u; |
402 |
10650 |
d->out_u = 0; |
403 |
10650 |
d->namelen = 0; |
404 |
10650 |
break; |
405 |
|
|
406 |
|
case VHD_BUF: |
407 |
0 |
d->error = H2SE_ENHANCE_YOUR_CALM; |
408 |
0 |
break; |
409 |
|
|
410 |
|
default: |
411 |
0 |
WRONG("Unhandled return value"); |
412 |
0 |
break; |
413 |
|
} |
414 |
|
|
415 |
22825 |
if (d->error == H2SE_ENHANCE_YOUR_CALM) { |
416 |
0 |
d->out = d->reset; |
417 |
0 |
d->out_l = e - d->out; |
418 |
0 |
d->out_u = 0; |
419 |
0 |
assert(d->out_l > 0); |
420 |
22825 |
} else if (d->error) |
421 |
750 |
break; |
422 |
|
} |
423 |
|
|
424 |
3850 |
if (d->error == H2SE_ENHANCE_YOUR_CALM) |
425 |
0 |
return (0); /* Stream error, delay reporting until |
426 |
|
h2h_decode_fini so that we can process the |
427 |
|
complete header block */ |
428 |
3850 |
return (d->error); |
429 |
3850 |
} |