io-event 1.14.5 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6141a80c8923fb24a4073f61f45a2e2f8065e4f2b415108caa39229efe37d0b
4
- data.tar.gz: 298a552f835530283020945d5005912263d0dc3042271fbc5eeb84d3f00b8bda
3
+ metadata.gz: 9fe7dd3dfb4c546053629b992abb50bbeda10d0149fc5b331c1e930379441478
4
+ data.tar.gz: cb1ba0e99fa681daa746d2c235beec989e2e6cb82d255dcc4ba18b0b85178544
5
5
  SHA512:
6
- metadata.gz: 64566ccf2e42e38dde477984ae4c511d2c673b1869e266f49ecda467ddcccef28167a345dcca11126e05e0345ebe58b550981e1c135ca982a8ad7b56d5748418
7
- data.tar.gz: c56c85168138319c4c527843484bfbbd7a3b685e72bf546aa32e6c01d6476ccc32e3f073aa1062d8699e916eba1d78de7fe263caff00db376c522c220ddc1aa1
6
+ metadata.gz: 8b14c4daf4e03541e971447c19497a708cb3588a23fa14cd235517bc1932e27005f6eca8a9ffb7f509900b95f75135912ad936b04e6f0bdeaad1cf6e59321853
7
+ data.tar.gz: 827b0313e900636ee96016f905550f2443e522d769a2d1c93b165c0c1896a279a19bb895b094028dd1dd359f8cd29702c20fd4be500f5e3c03cc96e589bef07b
checksums.yaml.gz.sig CHANGED
Binary file
@@ -615,6 +615,11 @@ VALUE io_read_loop(VALUE _arguments) {
615
615
  size_t offset = arguments->offset;
616
616
  size_t total = 0;
617
617
 
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
+
618
623
  size_t maximum_size = size - offset;
619
624
  while (maximum_size) {
620
625
  ssize_t result = read(arguments->descriptor, (char*)base+offset, maximum_size);
@@ -713,6 +718,11 @@ VALUE io_write_loop(VALUE _arguments) {
713
718
  rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
714
719
  }
715
720
 
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
+
716
726
  size_t maximum_size = size - offset;
717
727
  while (maximum_size) {
718
728
  ssize_t result = write(arguments->descriptor, (char*)base+offset, maximum_size);
@@ -605,6 +605,11 @@ VALUE io_read_loop(VALUE _arguments) {
605
605
 
606
606
  if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu)\n", arguments->descriptor, length);
607
607
 
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
+
608
613
  size_t maximum_size = size - offset;
609
614
  while (maximum_size) {
610
615
  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) {
713
718
 
714
719
  if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu)\n", arguments->descriptor, length);
715
720
 
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
+
716
726
  size_t maximum_size = size - offset;
717
727
  while (maximum_size) {
718
728
  if (DEBUG_IO_WRITE) fprintf(stderr, "write(%d, +%ld, %ld, length=%zu)\n", arguments->descriptor, offset, maximum_size, length);
@@ -106,8 +106,19 @@ VALUE IO_Event_Selector_loop_resume(struct IO_Event_Selector *backend, VALUE fib
106
106
 
107
107
  VALUE IO_Event_Selector_loop_yield(struct IO_Event_Selector *backend)
108
108
  {
109
- // TODO Why is this assertion failing in async?
110
- // RUBY_ASSERT(backend->loop != IO_Event_Fiber_current());
109
+ // Under normal operation, a user fiber yields back to the event loop fiber.
110
+ // However, in some cases (e.g. blocking IO called from within the scheduler
111
+ // fiber itself), the current fiber may already be the loop fiber. In that case,
112
+ // transferring to ourselves would be a no-op in Ruby, but it signals a misuse:
113
+ // the event loop fiber should never need to yield to itself, as nothing else
114
+ // would be running to resume it. We return immediately rather than self-transferring.
115
+ if (backend->loop == IO_Event_Fiber_current()) {
116
+ // Uncomment to investigate the callsite that triggers this condition:
117
+ // rb_warning("IO_Event_Selector_loop_yield: current fiber is the loop fiber");
118
+ // rb_funcall(rb_mKernel, rb_intern("puts"), 1, rb_funcall(rb_cThread, rb_intern("current"), 0));
119
+ return Qnil;
120
+ }
121
+
111
122
  return IO_Event_Fiber_transfer(backend->loop, 0, NULL);
112
123
  }
113
124
 
@@ -710,6 +710,11 @@ VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE b
710
710
  size_t total = 0;
711
711
  off_t from = io_seekable(descriptor);
712
712
 
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
+
713
718
  size_t maximum_size = size - offset;
714
719
 
715
720
  // 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
775
780
  size_t total = 0;
776
781
  off_t from = NUM2OFFT(_from);
777
782
 
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
+
778
788
  size_t maximum_size = size - offset;
779
789
  while (maximum_size) {
780
790
  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
892
902
  rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
893
903
  }
894
904
 
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
+
895
910
  size_t maximum_size = size - offset;
896
911
  while (maximum_size) {
897
912
  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
947
962
  rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
948
963
  }
949
964
 
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
+
950
970
  size_t maximum_size = size - offset;
951
971
  while (maximum_size) {
952
972
  int result = io_write(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);
@@ -188,6 +188,11 @@ module IO::Event
188
188
  # @parameter length [Integer] The minimum number of bytes to read.
189
189
  # @parameter offset [Integer] The offset into the buffer to read to.
190
190
  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
+
191
196
  total = 0
192
197
 
193
198
  Selector.nonblock(io) do
@@ -218,6 +223,11 @@ module IO::Event
218
223
  # @parameter length [Integer] The minimum number of bytes to write.
219
224
  # @parameter offset [Integer] The offset into the buffer to write from.
220
225
  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
+
221
231
  total = 0
222
232
 
223
233
  Selector.nonblock(io) do
@@ -278,12 +288,14 @@ module IO::Event
278
288
  duration = 0
279
289
  end
280
290
 
291
+ closed = nil
281
292
  readable = Array.new
282
293
  writable = Array.new
283
294
  priority = Array.new
284
295
 
285
296
  @waiting.delete_if do |io, waiter|
286
297
  if io.closed?
298
+ (closed ||= Array.new) << waiter
287
299
  true
288
300
  else
289
301
  waiter.each do |fiber, events|
@@ -304,6 +316,14 @@ module IO::Event
304
316
  end
305
317
  end
306
318
 
319
+ closed&.each do |waiter|
320
+ waiter.each do |fiber, _|
321
+ fiber.raise(IOError, "closed stream") if fiber.alive?
322
+ rescue
323
+ # The fiber didn't handle the exception; it is now terminated.
324
+ end
325
+ end
326
+
307
327
  duration = 0 unless @ready.empty?
308
328
  error = nil
309
329
 
@@ -328,7 +348,26 @@ module IO::Event
328
348
  end
329
349
 
330
350
  if error
331
- # Requeue the error into the pending exception queue:
351
+ # `IO.select` can raise both IOError and Errno::EBADF when one of the given IOs is closed. In that case, we enumerate all waiting IOs to find the closed one(s) and raise on their waiters. Then, we return 0 so the event loop retries cleanly.
352
+ if error.is_a?(IOError) || error.is_a?(Errno::EBADF)
353
+ closed = []
354
+ @waiting.delete_if do |io, waiter|
355
+ if io.closed?
356
+ waiter.each{|fiber, _| closed << fiber if fiber.alive?}
357
+ true
358
+ end
359
+ end
360
+
361
+ closed.each do |fiber|
362
+ fiber.raise(IOError, "closed stream")
363
+ rescue
364
+ # The fiber didn't handle the exception; it is now terminated.
365
+ end
366
+
367
+ return 0
368
+ end
369
+
370
+ # For all other errors (e.g. thread interrupts), re-queue on the scheduler thread:
332
371
  Thread.current.raise(error)
333
372
  return 0
334
373
  end
@@ -336,15 +375,17 @@ module IO::Event
336
375
  ready = Hash.new(0).compare_by_identity
337
376
 
338
377
  readable&.each do |io|
339
- ready[io] |= IO::READABLE
378
+ # Skip any IO that was closed/reused after IO.select returned - its fd number
379
+ # may now belong to a different file, so resuming the waiter would be wrong:
380
+ ready[io] |= IO::READABLE unless io.closed?
340
381
  end
341
382
 
342
383
  writable&.each do |io|
343
- ready[io] |= IO::WRITABLE
384
+ ready[io] |= IO::WRITABLE unless io.closed?
344
385
  end
345
386
 
346
387
  priority&.each do |io|
347
- ready[io] |= IO::PRIORITY
388
+ ready[io] |= IO::PRIORITY unless io.closed?
348
389
  end
349
390
 
350
391
  ready.each do |io, events|
@@ -7,6 +7,6 @@
7
7
  class IO
8
8
  # @namespace
9
9
  module Event
10
- VERSION = "1.14.5"
10
+ VERSION = "1.15.0"
11
11
  end
12
12
  end
data/readme.md CHANGED
@@ -18,6 +18,10 @@ Please see the [project documentation](https://socketry.github.io/io-event/) for
18
18
 
19
19
  Please see the [project releases](https://socketry.github.io/io-event/releases/index) for all releases.
20
20
 
21
+ ### v1.15.0
22
+
23
+ - 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.
24
+
21
25
  ### v1.14.4
22
26
 
23
27
  - Allow `epoll_pwait2` to be disabled via `--disable-epoll_pwait2`.
@@ -55,10 +59,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
55
59
 
56
60
  - Improved `IO::Event::Profiler` for detecting stalls.
57
61
 
58
- ### v1.8.0
59
-
60
- - Detecting fibers that are stalling the event loop.
61
-
62
62
  ## Contributing
63
63
 
64
64
  We welcome contributions to this project.
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v1.15.0
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
+
3
7
  ## v1.14.4
4
8
 
5
9
  - Allow `epoll_pwait2` to be disabled via `--disable-epoll_pwait2`.
data.tar.gz.sig CHANGED
@@ -1,2 +1,2 @@
1
- ����7��$�Y�2+4�5�|� �������a8����:�{,�!���.��1+�+ #`r1jH=!>>{Dp�C�gZs�_�JJ(�*�-�s_v+
2
- e��)�E`�uAA���hf5�#����K5�pu�1&����?Kg�|�| �P$2�d����&pl�R��66\��8�硍~������-+S�kW���?�bMf�P��>����3|��=�)�=�e罉�����l����r���;f�`i++)iҊ��F���M��V�� Z�^�7P{}��<&��s4�b��sxt)d�D�U���'9�T}b���w(G�L
1
+ !�_�!Մ_M�C� � �qg�y��[2����|.�_ 6����@�e�ѲC\tK]~z���(��U��tx Iz@��,ﰛt��/7A&���5�Ų��Z��,��iq�����>�IŝC��!��uap?Z�Zfx4zj,ԿmX%����7I�-N~|*պV��͎��E#P�}�����*Y��'(��ōK�E��ɫen>�S^�b���QȶNw�M�<lDZm������[��+e�����]��C���kl} PR�$�_�j�;cC�����$�yt�嫭�&����՚��i-̎4IӔch)T [�d��e�N\�:���Kއ�E3�W�v(پD?�&�1��%�_��y���U�7�
2
+ ptnnn
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-event
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.14.5
4
+ version: 1.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
metadata.gz.sig CHANGED
Binary file