authorgravatar for readcuttingt@gmail.comTom Read Cutting <readcuttingt@gmail.com> 2022-12-11 11:53:52+00:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-09 15:04:10-07:00
logb5222f86eec68088b9bd83ad2f648654242082ec
treeefeb5346647a475de4743a42b07316f2c16eb5bb
parent7d0d99aac0c3e47ab4f82a43525a6f4a18334d43

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.

2 files changed, 104 insertions(+), 0 deletions(-)

lib/std/os.zig+4
...@@ -660,6 +660,7 @@ pub const ReadError = error{...@@ -660,6 +660,7 @@ pub const ReadError = error{
660/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.660/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
661/// The corresponding POSIX limit is `math.maxInt(isize)`.661/// The corresponding POSIX limit is `math.maxInt(isize)`.
662pub fn read(fd: fd_t, buf: []u8) ReadError!usize {662pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
663 if (buf.len == 0) return 0;
663 if (builtin.os.tag == .windows) {664 if (builtin.os.tag == .windows) {
664 return windows.ReadFile(fd, buf, null, std.io.default_mode);665 return windows.ReadFile(fd, buf, null, std.io.default_mode);
665 }666 }
...@@ -787,6 +788,7 @@ pub const PReadError = ReadError || error{Unseekable};...@@ -787,6 +788,7 @@ pub const PReadError = ReadError || error{Unseekable};
787/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.788/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
788/// The corresponding POSIX limit is `math.maxInt(isize)`.789/// The corresponding POSIX limit is `math.maxInt(isize)`.
789pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {790pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
791 if (buf.len == 0) return 0;
790 if (builtin.os.tag == .windows) {792 if (builtin.os.tag == .windows) {
791 return windows.ReadFile(fd, buf, offset, std.io.default_mode);793 return windows.ReadFile(fd, buf, offset, std.io.default_mode);
792 }794 }
...@@ -1045,6 +1047,7 @@ pub const WriteError = error{...@@ -1045,6 +1047,7 @@ pub const WriteError = error{
1045/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.1047/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
1046/// The corresponding POSIX limit is `math.maxInt(isize)`.1048/// The corresponding POSIX limit is `math.maxInt(isize)`.
1047pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {1049pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
1050 if (bytes.len == 0) return 0;
1048 if (builtin.os.tag == .windows) {1051 if (builtin.os.tag == .windows) {
1049 return windows.WriteFile(fd, bytes, null, std.io.default_mode);1052 return windows.WriteFile(fd, bytes, null, std.io.default_mode);
1050 }1053 }
...@@ -1197,6 +1200,7 @@ pub const PWriteError = WriteError || error{Unseekable};...@@ -1197,6 +1200,7 @@ pub const PWriteError = WriteError || error{Unseekable};
1197/// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL.1200/// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL.
1198/// The corresponding POSIX limit is `math.maxInt(isize)`.1201/// The corresponding POSIX limit is `math.maxInt(isize)`.
1199pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {1202pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
1203 if (bytes.len == 0) return 0;
1200 if (builtin.os.tag == .windows) {1204 if (builtin.os.tag == .windows) {
1201 return windows.WriteFile(fd, bytes, offset, std.io.default_mode);1205 return windows.WriteFile(fd, bytes, offset, std.io.default_mode);
1202 }1206 }
lib/std/os/test.zig+100
...@@ -1080,3 +1080,103 @@ test "isatty" {...@@ -1080,3 +1080,103 @@ test "isatty" {
1080 var file = try tmp.dir.createFile("foo", .{});1080 var file = try tmp.dir.createFile("foo", .{});
1081 try expectEqual(os.isatty(file.handle), false);1081 try expectEqual(os.isatty(file.handle), false);
1082}1082}
1083
1084test "read with empty buffer" {
1085 if (native_os == .wasi) return error.SkipZigTest;
1086
1087 var tmp = tmpDir(.{});
1088 defer tmp.cleanup();
1089
1090 var arena = ArenaAllocator.init(testing.allocator);
1091 defer arena.deinit();
1092 const allocator = arena.allocator();
1093
1094 // Get base abs path
1095 const base_path = blk: {
1096 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
1097 break :blk try fs.realpathAlloc(allocator, relative_path);
1098 };
1099
1100 var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
1101 var file = try fs.cwd().createFile(file_path, .{ .read = true });
1102 defer file.close();
1103
1104 var bytes = try allocator.alloc(u8, 0);
1105
1106 _ = try os.read(file.handle, bytes);
1107}
1108
1109test "pread with empty buffer" {
1110 if (native_os == .wasi) return error.SkipZigTest;
1111
1112 var tmp = tmpDir(.{});
1113 defer tmp.cleanup();
1114
1115 var arena = ArenaAllocator.init(testing.allocator);
1116 defer arena.deinit();
1117 const allocator = arena.allocator();
1118
1119 // Get base abs path
1120 const base_path = blk: {
1121 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
1122 break :blk try fs.realpathAlloc(allocator, relative_path);
1123 };
1124
1125 var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
1126 var file = try fs.cwd().createFile(file_path, .{ .read = true });
1127 defer file.close();
1128
1129 var bytes = try allocator.alloc(u8, 0);
1130
1131 _ = try os.pread(file.handle, bytes, 0);
1132}
1133
1134test "write with empty buffer" {
1135 if (native_os == .wasi) return error.SkipZigTest;
1136
1137 var tmp = tmpDir(.{});
1138 defer tmp.cleanup();
1139
1140 var arena = ArenaAllocator.init(testing.allocator);
1141 defer arena.deinit();
1142 const allocator = arena.allocator();
1143
1144 // Get base abs path
1145 const base_path = blk: {
1146 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
1147 break :blk try fs.realpathAlloc(allocator, relative_path);
1148 };
1149
1150 var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
1151 var file = try fs.cwd().createFile(file_path, .{});
1152 defer file.close();
1153
1154 var bytes = try allocator.alloc(u8, 0);
1155
1156 _ = try os.write(file.handle, bytes);
1157}
1158
1159test "pwrite with empty buffer" {
1160 if (native_os == .wasi) return error.SkipZigTest;
1161
1162 var tmp = tmpDir(.{});
1163 defer tmp.cleanup();
1164
1165 var arena = ArenaAllocator.init(testing.allocator);
1166 defer arena.deinit();
1167 const allocator = arena.allocator();
1168
1169 // Get base abs path
1170 const base_path = blk: {
1171 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
1172 break :blk try fs.realpathAlloc(allocator, relative_path);
1173 };
1174
1175 var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
1176 var file = try fs.cwd().createFile(file_path, .{});
1177 defer file.close();
1178
1179 var bytes = try allocator.alloc(u8, 0);
1180
1181 _ = try os.pwrite(file.handle, bytes, 0);
1182}