Skip to content

Commit aa47301

Browse files
committed
Add bounds check for offset.
1 parent 6b84f63 commit aa47301

7 files changed

Lines changed: 130 additions & 0 deletions

File tree

ext/io/event/selector/epoll.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,11 @@ VALUE io_read_loop(VALUE _arguments) {
615615
size_t offset = arguments->offset;
616616
size_t total = 0;
617617

618+
// Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
619+
if (offset > size) {
620+
return rb_fiber_scheduler_io_result(-1, EINVAL);
621+
}
622+
618623
size_t maximum_size = size - offset;
619624
while (maximum_size) {
620625
ssize_t result = read(arguments->descriptor, (char*)base+offset, maximum_size);
@@ -713,6 +718,11 @@ VALUE io_write_loop(VALUE _arguments) {
713718
rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
714719
}
715720

721+
// Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
722+
if (offset > size) {
723+
return rb_fiber_scheduler_io_result(-1, EINVAL);
724+
}
725+
716726
size_t maximum_size = size - offset;
717727
while (maximum_size) {
718728
ssize_t result = write(arguments->descriptor, (char*)base+offset, maximum_size);

ext/io/event/selector/kqueue.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,11 @@ VALUE io_read_loop(VALUE _arguments) {
605605

606606
if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu)\n", arguments->descriptor, length);
607607

608+
// Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
609+
if (offset > size) {
610+
return rb_fiber_scheduler_io_result(-1, EINVAL);
611+
}
612+
608613
size_t maximum_size = size - offset;
609614
while (maximum_size) {
610615
if (DEBUG_IO_READ) fprintf(stderr, "read(%d, +%ld, %ld)\n", arguments->descriptor, offset, maximum_size);
@@ -713,6 +718,11 @@ VALUE io_write_loop(VALUE _arguments) {
713718

714719
if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu)\n", arguments->descriptor, length);
715720

721+
// Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
722+
if (offset > size) {
723+
return rb_fiber_scheduler_io_result(-1, EINVAL);
724+
}
725+
716726
size_t maximum_size = size - offset;
717727
while (maximum_size) {
718728
if (DEBUG_IO_WRITE) fprintf(stderr, "write(%d, +%ld, %ld, length=%zu)\n", arguments->descriptor, offset, maximum_size, length);

ext/io/event/selector/uring.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,11 @@ VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE b
710710
size_t total = 0;
711711
off_t from = io_seekable(descriptor);
712712

713+
// Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
714+
if (offset > size) {
715+
return rb_fiber_scheduler_io_result(-1, EINVAL);
716+
}
717+
713718
size_t maximum_size = size - offset;
714719

715720
// Are we performing a non-blocking read?
@@ -775,6 +780,11 @@ VALUE IO_Event_Selector_URing_io_pread(VALUE self, VALUE fiber, VALUE io, VALUE
775780
size_t total = 0;
776781
off_t from = NUM2OFFT(_from);
777782

783+
// Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
784+
if (offset > size) {
785+
return rb_fiber_scheduler_io_result(-1, EINVAL);
786+
}
787+
778788
size_t maximum_size = size - offset;
779789
while (maximum_size) {
780790
int result = io_read(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);
@@ -892,6 +902,11 @@ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
892902
rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
893903
}
894904

905+
// Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
906+
if (offset > size) {
907+
return rb_fiber_scheduler_io_result(-1, EINVAL);
908+
}
909+
895910
size_t maximum_size = size - offset;
896911
while (maximum_size) {
897912
int result = io_write(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);
@@ -947,6 +962,11 @@ VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE
947962
rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
948963
}
949964

965+
// Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
966+
if (offset > size) {
967+
return rb_fiber_scheduler_io_result(-1, EINVAL);
968+
}
969+
950970
size_t maximum_size = size - offset;
951971
while (maximum_size) {
952972
int result = io_write(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);

lib/io/event/selector/select.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,11 @@ def io_select(readable, writable, priority, timeout)
188188
# @parameter length [Integer] The minimum number of bytes to read.
189189
# @parameter offset [Integer] The offset into the buffer to read to.
190190
def io_read(fiber, io, buffer, length, offset = 0)
191+
# Ensure offset is within the bounds of the buffer to avoid ArgumentError
192+
if offset > buffer.size
193+
return -Errno::EINVAL::Errno
194+
end
195+
191196
total = 0
192197

193198
Selector.nonblock(io) do
@@ -218,6 +223,11 @@ def io_read(fiber, io, buffer, length, offset = 0)
218223
# @parameter length [Integer] The minimum number of bytes to write.
219224
# @parameter offset [Integer] The offset into the buffer to write from.
220225
def io_write(fiber, io, buffer, length, offset = 0)
226+
# Ensure offset is within the bounds of the buffer to avoid ArgumentError
227+
if offset > buffer.size
228+
return -Errno::EINVAL::Errno
229+
end
230+
221231
total = 0
222232

223233
Selector.nonblock(io) do

releases.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Releases
22

3+
## Unreleased
4+
5+
- Add bounds checks, in the unlikely event of a user providing an invalid offset that exceeds the buffer size. This prevents potential memory corruption and ensures safe operation when using buffered IO methods.
6+
37
## v1.14.4
48

59
- Allow `epoll_pwait2` to be disabled via `--disable-epoll_pwait2`.

test/io/event/selector/buffered_io.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,36 @@ def be_again?
117117

118118
expect(result).to be_again?
119119
end
120+
121+
it "returns EINVAL when read offset exceeds buffer size" do
122+
skip_if_ruby_platform(/mswin|mingw|cygwin/)
123+
124+
buffer = IO::Buffer.new(64)
125+
126+
reader = Fiber.new do
127+
# Offset 128 exceeds buffer size of 64
128+
result = selector.io_read(Fiber.current, input, buffer, 1, 128)
129+
expect(result).to be == -Errno::EINVAL::Errno
130+
end
131+
132+
reader.transfer
133+
selector.select(0)
134+
end
135+
136+
it "returns EINVAL when write offset exceeds buffer size" do
137+
skip_if_ruby_platform(/mswin|mingw|cygwin/)
138+
139+
buffer = IO::Buffer.new(64)
140+
141+
writer = Fiber.new do
142+
# Offset 128 exceeds buffer size of 64
143+
result = selector.io_write(Fiber.current, output, buffer, 1, 128)
144+
expect(result).to be == -Errno::EINVAL::Errno
145+
end
146+
147+
writer.transfer
148+
selector.select(0)
149+
end
120150
end
121151
end
122152

test/io/event/selector/file_io.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,52 @@
9292

9393
expect(wait_result).to be == IO::WRITABLE
9494
end
95+
96+
it "returns EINVAL when read offset exceeds buffer size" do
97+
skip_if_ruby_platform(/mswin|mingw|cygwin/)
98+
99+
buffer = IO::Buffer.new(64)
100+
file.seek(0)
101+
102+
# Offset 128 exceeds buffer size of 64
103+
result = selector.io_read(Fiber.current, file, buffer, 1, 128)
104+
105+
expect(result).to be == -Errno::EINVAL::Errno
106+
end
107+
108+
it "returns EINVAL when write offset exceeds buffer size" do
109+
skip_if_ruby_platform(/mswin|mingw|cygwin/)
110+
111+
buffer = IO::Buffer.new(64)
112+
file.seek(0)
113+
114+
# Offset 128 exceeds buffer size of 64
115+
result = selector.io_write(Fiber.current, file, buffer, 1, 128)
116+
117+
expect(result).to be == -Errno::EINVAL::Errno
118+
end
119+
120+
it "returns EINVAL when pread offset exceeds buffer size" do
121+
skip "io_pread is not implemented" unless selector.respond_to?(:io_pread)
122+
123+
buffer = IO::Buffer.new(64)
124+
125+
# Offset 128 exceeds buffer size of 64
126+
result = selector.io_pread(Fiber.current, file, buffer, 0, 1, 128)
127+
128+
expect(result).to be == -Errno::EINVAL::Errno
129+
end
130+
131+
it "returns EINVAL when pwrite offset exceeds buffer size" do
132+
skip "io_pwrite is not implemented" unless selector.respond_to?(:io_pwrite)
133+
134+
buffer = IO::Buffer.new(64)
135+
136+
# Offset 128 exceeds buffer size of 64
137+
result = selector.io_pwrite(Fiber.current, file, buffer, 0, 1, 128)
138+
139+
expect(result).to be == -Errno::EINVAL::Errno
140+
end
95141
end
96142
end
97143

0 commit comments

Comments
 (0)