From b5222f86eec68088b9bd83ad2f648654242082ec Mon Sep 17 00:00:00 2001 From: Tom Maenan Read Cutting Date: Sun, 11 Dec 2022 11:53:52 +0000 Subject: [PATCH] Add 0-length buffer checks to os.read & os.write This helps prevent errors related to undefined pointers being passed through to some OS apis when slices have 0 length. Tests have also been added to catch these cases. --- lib/std/os.zig | 4 ++ lib/std/os/test.zig | 100 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/lib/std/os.zig b/lib/std/os.zig index 172218a2ab..7406562f6a 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -660,6 +660,7 @@ pub const ReadError = error{ /// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. /// The corresponding POSIX limit is `math.maxInt(isize)`. pub fn read(fd: fd_t, buf: []u8) ReadError!usize { + if (buf.len == 0) return 0; if (builtin.os.tag == .windows) { return windows.ReadFile(fd, buf, null, std.io.default_mode); } @@ -787,6 +788,7 @@ pub const PReadError = ReadError || error{Unseekable}; /// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. /// The corresponding POSIX limit is `math.maxInt(isize)`. pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { + if (buf.len == 0) return 0; if (builtin.os.tag == .windows) { return windows.ReadFile(fd, buf, offset, std.io.default_mode); } @@ -1045,6 +1047,7 @@ pub const WriteError = error{ /// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. /// The corresponding POSIX limit is `math.maxInt(isize)`. pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { + if (bytes.len == 0) return 0; if (builtin.os.tag == .windows) { return windows.WriteFile(fd, bytes, null, std.io.default_mode); } @@ -1197,6 +1200,7 @@ pub const PWriteError = WriteError || error{Unseekable}; /// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL. /// The corresponding POSIX limit is `math.maxInt(isize)`. pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { + if (bytes.len == 0) return 0; if (builtin.os.tag == .windows) { return windows.WriteFile(fd, bytes, offset, std.io.default_mode); } diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index fe1963349d..24ca02da81 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -1080,3 +1080,103 @@ test "isatty" { var file = try tmp.dir.createFile("foo", .{}); try expectEqual(os.isatty(file.handle), false); } + +test "read with empty buffer" { + if (native_os == .wasi) return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + // Get base abs path + const base_path = blk: { + const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); + break :blk try fs.realpathAlloc(allocator, relative_path); + }; + + var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); + var file = try fs.cwd().createFile(file_path, .{ .read = true }); + defer file.close(); + + var bytes = try allocator.alloc(u8, 0); + + _ = try os.read(file.handle, bytes); +} + +test "pread with empty buffer" { + if (native_os == .wasi) return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + // Get base abs path + const base_path = blk: { + const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); + break :blk try fs.realpathAlloc(allocator, relative_path); + }; + + var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); + var file = try fs.cwd().createFile(file_path, .{ .read = true }); + defer file.close(); + + var bytes = try allocator.alloc(u8, 0); + + _ = try os.pread(file.handle, bytes, 0); +} + +test "write with empty buffer" { + if (native_os == .wasi) return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + // Get base abs path + const base_path = blk: { + const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); + break :blk try fs.realpathAlloc(allocator, relative_path); + }; + + var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); + var file = try fs.cwd().createFile(file_path, .{}); + defer file.close(); + + var bytes = try allocator.alloc(u8, 0); + + _ = try os.write(file.handle, bytes); +} + +test "pwrite with empty buffer" { + if (native_os == .wasi) return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + // Get base abs path + const base_path = blk: { + const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); + break :blk try fs.realpathAlloc(allocator, relative_path); + }; + + var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); + var file = try fs.cwd().createFile(file_path, .{}); + defer file.close(); + + var bytes = try allocator.alloc(u8, 0); + + _ = try os.pwrite(file.handle, bytes, 0); +}