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.
This commit is contained in:
Tom Maenan Read Cutting
2022-12-11 11:53:52 +00:00
committed by Andrew Kelley
parent 7d0d99aac0
commit b5222f86ee
2 changed files with 104 additions and 0 deletions

View File

@@ -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);
}

View File

@@ -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);
}