Skip to content

Commit c49afdb

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 c49afdb

File tree

2 files changed

+104
-0
lines changed

2 files changed

+104
-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: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,3 +1073,103 @@ 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) return error.SkipZigTest;
1079+
1080+
var tmp = tmpDir(.{});
1081+
defer tmp.cleanup();
1082+
1083+
var arena = ArenaAllocator.init(testing.allocator);
1084+
defer arena.deinit();
1085+
const allocator = arena.allocator();
1086+
1087+
// Get base abs path
1088+
const base_path = blk: {
1089+
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
1090+
break :blk try fs.realpathAlloc(allocator, relative_path);
1091+
};
1092+
1093+
var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
1094+
var file = try fs.cwd().createFile(file_path, .{ .read = true });
1095+
defer file.close();
1096+
1097+
var bytes = try allocator.alloc(u8, 0);
1098+
1099+
_ = try os.read(file.handle, bytes);
1100+
}
1101+
1102+
test "pread with empty buffer" {
1103+
if (native_os == .wasi) return error.SkipZigTest;
1104+
1105+
var tmp = tmpDir(.{});
1106+
defer tmp.cleanup();
1107+
1108+
var arena = ArenaAllocator.init(testing.allocator);
1109+
defer arena.deinit();
1110+
const allocator = arena.allocator();
1111+
1112+
// Get base abs path
1113+
const base_path = blk: {
1114+
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
1115+
break :blk try fs.realpathAlloc(allocator, relative_path);
1116+
};
1117+
1118+
var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
1119+
var file = try fs.cwd().createFile(file_path, .{ .read = true });
1120+
defer file.close();
1121+
1122+
var bytes = try allocator.alloc(u8, 0);
1123+
1124+
_ = try os.pread(file.handle, bytes, 0);
1125+
}
1126+
1127+
test "write with empty buffer" {
1128+
if (native_os == .wasi) return error.SkipZigTest;
1129+
1130+
var tmp = tmpDir(.{});
1131+
defer tmp.cleanup();
1132+
1133+
var arena = ArenaAllocator.init(testing.allocator);
1134+
defer arena.deinit();
1135+
const allocator = arena.allocator();
1136+
1137+
// Get base abs path
1138+
const base_path = blk: {
1139+
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
1140+
break :blk try fs.realpathAlloc(allocator, relative_path);
1141+
};
1142+
1143+
var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
1144+
var file = try fs.cwd().createFile(file_path, .{});
1145+
defer file.close();
1146+
1147+
var bytes = try allocator.alloc(u8, 0);
1148+
1149+
_ = try os.write(file.handle, bytes);
1150+
}
1151+
1152+
test "pwrite with empty buffer" {
1153+
if (native_os == .wasi) return error.SkipZigTest;
1154+
1155+
var tmp = tmpDir(.{});
1156+
defer tmp.cleanup();
1157+
1158+
var arena = ArenaAllocator.init(testing.allocator);
1159+
defer arena.deinit();
1160+
const allocator = arena.allocator();
1161+
1162+
// Get base abs path
1163+
const base_path = blk: {
1164+
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
1165+
break :blk try fs.realpathAlloc(allocator, relative_path);
1166+
};
1167+
1168+
var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
1169+
var file = try fs.cwd().createFile(file_path, .{});
1170+
defer file.close();
1171+
1172+
var bytes = try allocator.alloc(u8, 0);
1173+
1174+
_ = try os.pwrite(file.handle, bytes, 0);
1175+
}

0 commit comments

Comments
 (0)