haproxy/src/fcgi.c
Youfu Zhang 2e6bf0a272 BUG/MAJOR: fcgi: Fix uninitialized reserved bytes
The output buffer is not zero-initialized. If we don't clear reserved
bytes, fcgi requests sent to backend will leak sensitive data.

This patch must be backported as far as 2.2.
2022-12-09 12:23:14 +01:00

295 lines
8.0 KiB
C

/*
* FastCGI protocol processing
*
* Copyright (C) 2019 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <haproxy/buf.h>
#include <haproxy/fcgi.h>
#include <haproxy/istbuf.h>
/* Encodes header of a FCGI record into the chunk <out>. It returns non-zero on
* success and 0 on failure (buffer full). <out> is a chunk, so the wrapping is
* not handled by this function. It is the caller responsibility to ensure
* enough contiguous space is available
*/
int fcgi_encode_record_hdr(struct buffer *out, const struct fcgi_header *h)
{
size_t len = out->data;
if (len + 8 >= b_size(out))
return 0;
out->area[len++] = h->vsn;
out->area[len++] = h->type;
out->area[len++] = ((h->id >> 8) & 0xff);
out->area[len++] = (h->id & 0xff);
out->area[len++] = ((h->len >> 8) & 0xff);
out->area[len++] = (h->len & 0xff);
out->area[len++] = h->padding;
out->area[len++] = 0; /* rsv */
out->data = len;
return 1;
}
/* Decodes a FCGI record header from offset <o> of buffer <in> into descriptor
* <h>. The buffer may wrap so each byte read must be checked. The header is
* formed like this :
*
* b0 b1 b2 b3 b4 b5 b6 b7
* +-----+------+-----+-----+------+------+--------+-----+
* | vsn | type | id1 | id0 | len1 | len0 | padlen | rsv |
* +-----+------+-----+-----+------+------+--------+-----+
*
* Returns zero if some bytes are missing, otherwise the number of read bytes.
*/
size_t fcgi_decode_record_hdr(const struct buffer *in, size_t o, struct fcgi_header *h)
{
if (b_data(in) < o + 8)
return 0;
h->vsn = (uint8_t)(*b_peek(in, o));
h->type = (uint8_t)(*b_peek(in, o+1));
h->id = ((uint8_t)(*b_peek(in, o+2)) << 8) + (uint8_t)(*b_peek(in, o+3));
h->len = ((uint8_t)(*b_peek(in, o+4)) << 8) + (uint8_t)(*b_peek(in, o+5));
h->padding = (uint8_t)(*b_peek(in, o+6));
/* ignore rsv */
return 8;
}
/* Encodes the payload part of a BEGIN_REQUEST record into the chunk <out>. It
* returns non-zero on success and 0 on failure (buffer full). <out> is a chunk,
* so the wrapping is not handled by this function. It is the caller
* responsibility to ensure enough contiguous space is available
*/
int fcgi_encode_begin_request(struct buffer *out, const struct fcgi_begin_request *r)
{
size_t len = out->data;
if (len + 8 >= b_size(out))
return 0;
out->area[len++] = ((r->role >> 8) & 0xff);
out->area[len++] = (r->role & 0xff);
out->area[len++] = r->flags;
out->area[len++] = 0; /* rsv */
out->area[len++] = 0;
out->area[len++] = 0;
out->area[len++] = 0;
out->area[len++] = 0;
out->data = len;
return 1;
}
/* Encodes a parameter, part of the payload of a PARAM record, into the chunk
* <out>. It returns non-zero on success and 0 on failure (buffer full). <out>
* is a chunk, so the wrapping is not handled by this function. It is the caller
* responsibility to ensure enough contiguous space is available. The
* parameter's name is converted to upper case and non-alphanumeric character
* are replaced by an underscore.
*/
int fcgi_encode_param(struct buffer *out, const struct fcgi_param *p)
{
size_t off, len = out->data;
int nbytes, vbytes;
nbytes = (!(p->n.len >> 7) ? 1 : 4);
vbytes = (!(p->v.len >> 7) ? 1 : 4);
if ((len + nbytes + p->n.len + vbytes + p->v.len) >= b_size(out))
return 0;
if (nbytes == 1)
out->area[len++] = (p->n.len & 0xff);
else {
out->area[len++] = (((p->n.len >> 24) & 0xff) | 0x80);
out->area[len++] = ((p->n.len >> 16) & 0xff);
out->area[len++] = ((p->n.len >> 8) & 0xff);
out->area[len++] = (p->n.len & 0xff);
}
if (vbytes == 1)
out->area[len++] = (p->v.len & 0xff);
else {
out->area[len++] = (((p->v.len >> 24) & 0xff) | 0x80);
out->area[len++] = ((p->v.len >> 16) & 0xff);
out->area[len++] = ((p->v.len >> 8) & 0xff);
out->area[len++] = (p->v.len & 0xff);
}
for (off = 0; off < p->n.len; off++) {
if (isalnum((unsigned char)p->n.ptr[off]))
out->area[len++] = ist_uc[(unsigned char)p->n.ptr[off]];
else
out->area[len++] = '_';
}
if (p->v.len) {
ist2bin(out->area + len, p->v);
len += p->v.len;
}
out->data = len;
return 1;
}
/* Decodes a parameter of a PARAM record from offset <o> of buffer <in> into the
* FCGI param <p>. The buffer may wrap so each byte read must be checked.
* Returns zero if some bytes are missing, otherwise the number of read bytes.
*/
size_t fcgi_decode_param(const struct buffer *in, size_t o, struct fcgi_param *p)
{
size_t data = b_data(in);
size_t nlen, vlen, len = 0;
uint8_t b0, b1, b2, b3;
if (data < o + 1)
return 0;
b0 = *b_peek(in, o++);
if (!(b0 >> 7)) {
nlen = b0;
len++;
}
else {
if (data < o + 3)
return 0;
b1 = *b_peek(in, o++);
b2 = *b_peek(in, o++);
b3 = *b_peek(in, o++);
nlen = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
len += 4;
}
if (data < o + 1)
return 0;
b0 = *b_peek(in, o++);
if (!(b0 >> 7)) {
vlen = b0;
len++;
}
else {
if (data < o + 3)
return 0;
b1 = *b_peek(in, o++);
b2 = *b_peek(in, o++);
b3 = *b_peek(in, o++);
vlen = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
len += 4;
}
if (data < nlen + vlen)
return 0;
p->n = ist2(b_peek(in, o), nlen);
p->v = ist2(b_peek(in, o + nlen), vlen);
len += nlen + vlen;
return len;
}
/* Decodes a parameter of a PARAM record from offset <o> of buffer <in> into the
* FCGI param <p>. To call this function, the buffer must not wrap. Returns zero
* if some bytes are missing, otherwise the number of read bytes.
*/
size_t fcgi_aligned_decode_param(const struct buffer *in, size_t o, struct fcgi_param *p)
{
size_t data = b_data(in);
size_t nlen, vlen, len = 0;
uint8_t b0, b1, b2, b3;
if (data < o + 1)
return 0;
b0 = in->area[o++];
if (!(b0 >> 7)) {
nlen = b0;
len++;
}
else {
if (data < o + 3)
return 0;
b1 = in->area[o++];
b2 = in->area[o++];
b3 = in->area[o++];
nlen = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
len += 4;
}
if (data < o + 1)
return 0;
b0 = in->area[o++];
if (!(b0 >> 7)) {
vlen = b0;
len++;
}
else {
if (data < o + 3)
return 0;
b1 = in->area[o++];
b2 = in->area[o++];
b3 = in->area[o++];
vlen = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
len += 4;
}
if (data < nlen + vlen)
return 0;
p->n = ist2(in->area + o, nlen);
p->v = ist2(in->area + o + nlen, vlen);
len += nlen + vlen;
return len;
}
/* Decodes payload of a END_REQUEST record from offset <o> of buffer <in> into
* the FCGI param <p>. The buffer may wrap so each byte read must be
* checked. Returns zero if some bytes are missing, otherwise the number of read
* bytes.
*/
size_t fcgi_decode_end_request(const struct buffer *in, size_t o, struct fcgi_end_request *rec)
{
uint8_t b0, b1, b2, b3;
if (b_data(in) < o + 8)
return 0;
b0 = *b_peek(in, o++);
b1 = *b_peek(in, o++);
b2 = *b_peek(in, o++);
b3 = *b_peek(in, o++);
rec->status = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
rec->errcode = *b_peek(in, o++);
o += 3; /* ignore rsv */
return 8;
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/