Skip to content

Commit 301d91c

Browse files
committed
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.
1 parent 9bcfe55 commit 301d91c

File tree

2 files changed

+90
-0
lines changed

2 files changed

+90
-0
lines changed

lib/std/os.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ pub const ReadError = error{
660660
/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
661661
/// The corresponding POSIX limit is `math.maxInt(isize)`.
662662
pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
663+
if (buf.len == 0) return 0;
663664
if (builtin.os.tag == .windows) {
664665
return windows.ReadFile(fd, buf, null, std.io.default_mode);
665666
}
@@ -787,6 +788,7 @@ pub const PReadError = ReadError || error{Unseekable};
787788
/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
788789
/// The corresponding POSIX limit is `math.maxInt(isize)`.
789790
pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
791+
if (buf.len == 0) return 0;
790792
if (builtin.os.tag == .windows) {
791793
return windows.ReadFile(fd, buf, offset, std.io.default_mode);
792794
}
@@ -1045,6 +1047,7 @@ pub const WriteError = error{
10451047
/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
10461048
/// The corresponding POSIX limit is `math.maxInt(isize)`.
10471049
pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
1050+
if (bytes.len == 0) return 0;
10481051
if (builtin.os.tag == .windows) {
10491052
return windows.WriteFile(fd, bytes, null, std.io.default_mode);
10501053
}
@@ -1197,6 +1200,7 @@ pub const PWriteError = WriteError || error{Unseekable};
11971200
/// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL.
11981201
/// The corresponding POSIX limit is `math.maxInt(isize)`.
11991202
pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
1203+
if (bytes.len == 0) return 0;
12001204
if (builtin.os.tag == .windows) {
12011205
return windows.WriteFile(fd, bytes, offset, std.io.default_mode);
12021206
}

lib/std/os/test.zig

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,3 +1073,89 @@ test "isatty" {
10731073
var file = try tmp.dir.createFile("foo", .{});
10741074
try expectEqual(os.isatty(file.handle), false);
10751075
}
1076+
1077+
test "read with empty buffer" {
1078+
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1079+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
1080+
1081+
var arena = ArenaAllocator.init(testing.allocator);
1082+
defer arena.deinit();
1083+
const allocator = arena.allocator();
1084+
1085+
var bytes = try allocator.alloc(u8, 0);
1086+
defer testing.allocator.free(bytes);
1087+
// Open build.zig as we are just reading it anyway, and it's a text file
1088+
// that definitely exists.
1089+
var file = try fs.cwd().openFile("build.zig", .{ .mode = .read_only });
1090+
defer file.close();
1091+
_ = try os.read(file.handle, bytes);
1092+
}
1093+
1094+
test "pread with empty buffer" {
1095+
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1096+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
1097+
1098+
var arena = ArenaAllocator.init(testing.allocator);
1099+
defer arena.deinit();
1100+
const allocator = arena.allocator();
1101+
1102+
var bytes = try allocator.alloc(u8, 0);
1103+
defer testing.allocator.free(bytes);
1104+
// Open build.zig as we are just reading it anyway, and it's a text file
1105+
// that definitely exists.
1106+
var file = try fs.cwd().openFile("build.zig", .{ .mode = .read_only });
1107+
defer file.close();
1108+
_ = try os.pread(file.handle, bytes, 0);
1109+
}
1110+
1111+
test "write with empty buffer" {
1112+
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1113+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
1114+
1115+
var tmp = tmpDir(.{});
1116+
defer tmp.cleanup();
1117+
1118+
var arena = ArenaAllocator.init(testing.allocator);
1119+
defer arena.deinit();
1120+
const allocator = arena.allocator();
1121+
1122+
// Get base abs path
1123+
const base_path = blk: {
1124+
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
1125+
break :blk try fs.realpathAlloc(allocator, relative_path);
1126+
};
1127+
1128+
var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
1129+
var file = try fs.cwd().createFile(file_path, .{});
1130+
defer file.close();
1131+
1132+
var bytes = try allocator.alloc(u8, 0);
1133+
1134+
_ = try os.write(file.handle, bytes);
1135+
}
1136+
1137+
test "pwrite with empty buffer" {
1138+
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1139+
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
1140+
1141+
var tmp = tmpDir(.{});
1142+
defer tmp.cleanup();
1143+
1144+
var arena = ArenaAllocator.init(testing.allocator);
1145+
defer arena.deinit();
1146+
const allocator = arena.allocator();
1147+
1148+
// Get base abs path
1149+
const base_path = blk: {
1150+
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
1151+
break :blk try fs.realpathAlloc(allocator, relative_path);
1152+
};
1153+
1154+
var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
1155+
var file = try fs.cwd().createFile(file_path, .{});
1156+
defer file.close();
1157+
1158+
var bytes = try allocator.alloc(u8, 0);
1159+
1160+
_ = try os.pwrite(file.handle, bytes, 0);
1161+
}

0 commit comments

Comments
 (0)