HTTP/2: make ngx_http_huff_encode() output path alignment-safe

In ngx_http_huff_encode(), the original output path used
ngx_http_huff_encode_buf(dst, buf), which may expand to wide stores through
(uint64_t *) or (uint32_t *) casts on the destination byte buffer.

On an aarch64 cross-compiled environment, this was observed to corrupt some
HTTP/2 response headers such as Date.

This change preserves the existing HPACK Huffman encoding semantics and only
changes how encoded bytes are written to dst:

- keep the existing code/len table lookup logic
- keep encoded length calculation unchanged
- keep RFC 7541 padding behavior unchanged
- replace architecture-sensitive wide-store output with byte-wise writes

The new implementation accumulates bits in buf and emits bytes sequentially
through u_char *dst, avoiding alignment-sensitive stores on the destination
buffer and improving portability across cross-toolchains and architectures.
This commit is contained in:
최대우 (Daewoo Choi) 2026-05-06 16:57:35 +09:00 committed by GitHub
parent 3b5da468b3
commit fde95f8243
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -156,99 +156,56 @@ static ngx_http_huff_encode_code_t ngx_http_huff_encode_table_lc[256] =
};
#if (NGX_PTR_SIZE == 8)
#if (NGX_HAVE_LITTLE_ENDIAN)
#if (NGX_HAVE_GCC_BSWAP64)
#define ngx_http_huff_encode_buf(dst, buf) \
(*(uint64_t *) (dst) = __builtin_bswap64(buf))
#else
#define ngx_http_huff_encode_buf(dst, buf) \
((dst)[0] = (u_char) ((buf) >> 56), \
(dst)[1] = (u_char) ((buf) >> 48), \
(dst)[2] = (u_char) ((buf) >> 40), \
(dst)[3] = (u_char) ((buf) >> 32), \
(dst)[4] = (u_char) ((buf) >> 24), \
(dst)[5] = (u_char) ((buf) >> 16), \
(dst)[6] = (u_char) ((buf) >> 8), \
(dst)[7] = (u_char) (buf))
#endif
#else /* !NGX_HAVE_LITTLE_ENDIAN */
#define ngx_http_huff_encode_buf(dst, buf) \
(*(uint64_t *) (dst) = (buf))
#endif
#else /* NGX_PTR_SIZE == 4 */
#define ngx_http_huff_encode_buf(dst, buf) \
(*(uint32_t *) (dst) = htonl(buf))
#endif
size_t
ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, ngx_uint_t lower)
{
u_char *end;
size_t hlen;
ngx_uint_t buf, pending, code;
ngx_http_huff_encode_code_t *table, *next;
u_char ch;
size_t hlt, pending;
ngx_uint_t i;
uint64_t buf;
ngx_http_huff_encode_code_t *table, *code;
table = lower ? ngx_http_huff_encode_table_lc
: ngx_http_huff_encode_table;
hlen = 0;
hlt = 0;
for (i = 0; i < len; i++) {
ch = src[i];
hlt += table[ch].len;
}
hlt = (hlt + 7) / 8;
if (dst == NULL) {
return hlt;
}
buf = 0;
pending = 0;
end = src + len;
for (i = 0; i < len; i++) {
ch = src[i];
code = &table[ch];
while (src != end) {
next = &table[*src++];
buf <<= code->len;
buf |= code->code;
pending += code->len;
code = next->code;
pending += next->len;
/* accumulate bits */
if (pending < sizeof(buf) * 8) {
buf |= code << (sizeof(buf) * 8 - pending);
continue;
/*
* Emit encoded output one byte at a time to avoid
* alignment-sensitive wide stores on the destination buffer.
*/
while (pending >= 8) {
pending -= 8;
*dst++ = (u_char) (buf >> pending);
}
if (hlen + sizeof(buf) >= len) {
return 0;
}
pending -= sizeof(buf) * 8;
buf |= code >> pending;
ngx_http_huff_encode_buf(&dst[hlen], buf);
hlen += sizeof(buf);
buf = pending ? code << (sizeof(buf) * 8 - pending) : 0;
}
if (pending == 0) {
return hlen;
if (pending > 0) {
buf <<= (8 - pending);
buf |= (uint64_t) (0xff >> pending);
*dst++ = (u_char) buf;
}
buf |= (ngx_uint_t) -1 >> pending;
pending = ngx_align(pending, 8);
if (hlen + pending / 8 >= len) {
return 0;
}
buf >>= sizeof(buf) * 8 - pending;
do {
pending -= 8;
dst[hlen++] = (u_char) (buf >> pending);
} while (pending);
return hlen;
return hlt;
}