authorgravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-10-17 19:08:22-05:00
committergravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-10-21 20:52:59-05:00
log7dd3099519fd0f64fcdf11791fc9ba95a68e0637
treefadcb414b73af472573a12e3e5abba1ce1ac6ba0
parent363d0ee5e13f4ac3a93d246121edfd00ef9fd97b
signaturelock-open Commit is signed but in an unrecognized format.

std.http: fix crashes found via fuzzing


4 files changed, 33 insertions(+), 11 deletions(-)

lib/std/http.zig+2-1
...@@ -35,7 +35,8 @@ pub const Method = enum(u64) { // TODO: should be u192 or u256, but neither is s...@@ -35,7 +35,8 @@ pub const Method = enum(u64) { // TODO: should be u192 or u256, but neither is s
35 /// Asserts that `s` is 24 or fewer bytes.35 /// Asserts that `s` is 24 or fewer bytes.
36 pub fn parse(s: []const u8) u64 {36 pub fn parse(s: []const u8) u64 {
37 var x: u64 = 0;37 var x: u64 = 0;
38 @memcpy(std.mem.asBytes(&x)[0..s.len], s);38 const len = @min(s.len, @sizeOf(@TypeOf(x)));
39 @memcpy(std.mem.asBytes(&x)[0..len], s[0..len]);
39 return x;40 return x;
40 }41 }
4142
lib/std/http/Client.zig+23-5
...@@ -15,7 +15,7 @@ const proto = @import("protocol.zig");...@@ -15,7 +15,7 @@ const proto = @import("protocol.zig");
15pub const disable_tls = std.options.http_disable_tls;15pub const disable_tls = std.options.http_disable_tls;
1616
17allocator: Allocator,17allocator: Allocator,
18ca_bundle: std.crypto.Certificate.Bundle = .{},18ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{},
19ca_bundle_mutex: std.Thread.Mutex = .{},19ca_bundle_mutex: std.Thread.Mutex = .{},
2020
21/// When this is `true`, the next time this client performs an HTTPS request,21/// When this is `true`, the next time this client performs an HTTPS request,
...@@ -386,7 +386,7 @@ pub const Response = struct {...@@ -386,7 +386,7 @@ pub const Response = struct {
386 };386 };
387387
388 pub fn parse(res: *Response, bytes: []const u8, trailing: bool) ParseError!void {388 pub fn parse(res: *Response, bytes: []const u8, trailing: bool) ParseError!void {
389 var it = mem.tokenizeAny(u8, bytes[0 .. bytes.len - 4], "\r\n");389 var it = mem.tokenizeAny(u8, bytes, "\r\n");
390390
391 const first_line = it.next() orelse return error.HttpHeadersInvalid;391 const first_line = it.next() orelse return error.HttpHeadersInvalid;
392 if (first_line.len < 12)392 if (first_line.len < 12)
...@@ -405,6 +405,8 @@ pub const Response = struct {...@@ -405,6 +405,8 @@ pub const Response = struct {
405 res.status = status;405 res.status = status;
406 res.reason = reason;406 res.reason = reason;
407407
408 res.headers.clearRetainingCapacity();
409
408 while (it.next()) |line| {410 while (it.next()) |line| {
409 if (line.len == 0) return error.HttpHeadersInvalid;411 if (line.len == 0) return error.HttpHeadersInvalid;
410 switch (line[0]) {412 switch (line[0]) {
...@@ -525,6 +527,7 @@ pub const Request = struct {...@@ -525,6 +527,7 @@ pub const Request = struct {
525527
526 redirects_left: u32,528 redirects_left: u32,
527 handle_redirects: bool,529 handle_redirects: bool,
530 handle_continue: bool,
528531
529 response: Response,532 response: Response,
530533
...@@ -758,6 +761,10 @@ pub const Request = struct {...@@ -758,6 +761,10 @@ pub const Request = struct {
758 if (req.response.status == .@"continue") {761 if (req.response.status == .@"continue") {
759 req.response.parser.done = true; // we're done parsing the continue response, reset to prepare for the real response762 req.response.parser.done = true; // we're done parsing the continue response, reset to prepare for the real response
760 req.response.parser.reset();763 req.response.parser.reset();
764
765 if (req.handle_continue)
766 continue;
767
761 break;768 break;
762 }769 }
763770
...@@ -897,8 +904,6 @@ pub const Request = struct {...@@ -897,8 +904,6 @@ pub const Request = struct {
897 }904 }
898905
899 if (has_trail) {906 if (has_trail) {
900 req.response.headers.clearRetainingCapacity();
901
902 // The response headers before the trailers are already guaranteed to be valid, so they will always be parsed again and cannot return an error.907 // The response headers before the trailers are already guaranteed to be valid, so they will always be parsed again and cannot return an error.
903 // This will *only* fail for a malformed trailer.908 // This will *only* fail for a malformed trailer.
904 req.response.parse(req.response.parser.header_bytes.items, true) catch return error.InvalidTrailers;909 req.response.parse(req.response.parser.header_bytes.items, true) catch return error.InvalidTrailers;
...@@ -999,7 +1004,9 @@ pub fn deinit(client: *Client) void {...@@ -999,7 +1004,9 @@ pub fn deinit(client: *Client) void {
999 proxy.headers.deinit();1004 proxy.headers.deinit();
1000 }1005 }
10011006
1002 client.ca_bundle.deinit(client.allocator);1007 if (!disable_tls)
1008 client.ca_bundle.deinit(client.allocator);
1009
1003 client.* = undefined;1010 client.* = undefined;
1004}1011}
10051012
...@@ -1315,6 +1322,14 @@ pub const RequestError = ConnectTcpError || ConnectErrorPartial || Request.SendE...@@ -1315,6 +1322,14 @@ pub const RequestError = ConnectTcpError || ConnectErrorPartial || Request.SendE
1315pub const RequestOptions = struct {1322pub const RequestOptions = struct {
1316 version: http.Version = .@"HTTP/1.1",1323 version: http.Version = .@"HTTP/1.1",
13171324
1325 /// Automatically ignore 100 Continue responses. This assumes you don't care, and will have sent the body before you
1326 /// wait for the response.
1327 ///
1328 /// If this is not the case AND you know the server will send a 100 Continue, set this to false and wait for a
1329 /// response before sending the body. If you wait AND the server does not send a 100 Continue before you finish the
1330 /// request, then the request *will* deadlock.
1331 handle_continue: bool = true,
1332
1318 handle_redirects: bool = true,1333 handle_redirects: bool = true,
1319 max_redirects: u32 = 3,1334 max_redirects: u32 = 3,
1320 header_strategy: StorageStrategy = .{ .dynamic = 16 * 1024 },1335 header_strategy: StorageStrategy = .{ .dynamic = 16 * 1024 },
...@@ -1361,6 +1376,8 @@ pub fn open(client: *Client, method: http.Method, uri: Uri, headers: http.Header...@@ -1361,6 +1376,8 @@ pub fn open(client: *Client, method: http.Method, uri: Uri, headers: http.Header
1361 const host = uri.host orelse return error.UriMissingHost;1376 const host = uri.host orelse return error.UriMissingHost;
13621377
1363 if (protocol == .tls and @atomicLoad(bool, &client.next_https_rescan_certs, .Acquire)) {1378 if (protocol == .tls and @atomicLoad(bool, &client.next_https_rescan_certs, .Acquire)) {
1379 if (disable_tls) unreachable;
1380
1364 client.ca_bundle_mutex.lock();1381 client.ca_bundle_mutex.lock();
1365 defer client.ca_bundle_mutex.unlock();1382 defer client.ca_bundle_mutex.unlock();
13661383
...@@ -1381,6 +1398,7 @@ pub fn open(client: *Client, method: http.Method, uri: Uri, headers: http.Header...@@ -1381,6 +1398,7 @@ pub fn open(client: *Client, method: http.Method, uri: Uri, headers: http.Header
1381 .version = options.version,1398 .version = options.version,
1382 .redirects_left = options.max_redirects,1399 .redirects_left = options.max_redirects,
1383 .handle_redirects = options.handle_redirects,1400 .handle_redirects = options.handle_redirects,
1401 .handle_continue = options.handle_continue,
1384 .response = .{1402 .response = .{
1385 .status = undefined,1403 .status = undefined,
1386 .reason = undefined,1404 .reason = undefined,
lib/std/http/Headers.zig+7-4
...@@ -14,15 +14,18 @@ pub const CaseInsensitiveStringContext = struct {...@@ -14,15 +14,18 @@ pub const CaseInsensitiveStringContext = struct {
14 pub fn hash(self: @This(), s: []const u8) u64 {14 pub fn hash(self: @This(), s: []const u8) u64 {
15 _ = self;15 _ = self;
16 var buf: [64]u8 = undefined;16 var buf: [64]u8 = undefined;
17 var i: u8 = 0;17 var i: usize = 0;
1818
19 var h = std.hash.Wyhash.init(0);19 var h = std.hash.Wyhash.init(0);
20 while (i < s.len) : (i += 64) {20 while (i + 64 < s.len) : (i += 64) {
21 const left = @min(64, s.len - i);21 const ret = ascii.lowerString(buf[0..], s[i..][0..64]);
22 const ret = ascii.lowerString(buf[0..], s[i..][0..left]);
23 h.update(ret);22 h.update(ret);
24 }23 }
2524
25 const left = @min(64, s.len - i);
26 const ret = ascii.lowerString(buf[0..], s[i..][0..left]);
27 h.update(ret);
28
26 return h.final();29 return h.final();
27 }30 }
2831
lib/std/http/Server.zig+1-1
...@@ -178,7 +178,7 @@ pub const Request = struct {...@@ -178,7 +178,7 @@ pub const Request = struct {
178 };178 };
179179
180 pub fn parse(req: *Request, bytes: []const u8) ParseError!void {180 pub fn parse(req: *Request, bytes: []const u8) ParseError!void {
181 var it = mem.tokenizeAny(u8, bytes[0 .. bytes.len - 4], "\r\n");181 var it = mem.tokenizeAny(u8, bytes, "\r\n");
182182
183 const first_line = it.next() orelse return error.HttpHeadersInvalid;183 const first_line = it.next() orelse return error.HttpHeadersInvalid;
184 if (first_line.len < 10)184 if (first_line.len < 10)