Repository: libuv/libuv (C)

Issue:
uv_loop_init abort if not enough memory instead of returning UV_ENOMEM
I've been able to reproduce this on latest release v1.50.0 with the following code:

```c
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <uv.h>

int test_alloc_until_failure = 1;

void *test_malloc(size_t size) {
  if (test_alloc_until_failure <= 0)
    return NULL;
  test_alloc_until_failure--;

  return malloc(size);
}

void *test_realloc(void *ptr, size_t size) {
  if (test_alloc_until_failure <= 0)
    return NULL;
  test_alloc_until_failure--;

  return realloc(ptr, size);
}

void *test_calloc(size_t count, size_t size) {
  if (test_alloc_until_failure <= 0)
    return NULL;
  test_alloc_until_failure--;

  return calloc(count, size);
}

int main(void) {
  int r = uv_replace_allocator(test_malloc, test_realloc, test_calloc, free);
  if (r != 0) {
    printf("REPLACE ALLOC FAILED %s\n", strerror(-r));
    return r;
  }

  uv_loop_t loop = {0};
  r = uv_loop_init(&loop);
  if (r != 0) {
    printf("LOOP INIT FAILED %s\n", strerror(-r));
    return r;
  }

  return 69;
}
```

Expected:
```
LOOP INIT FAILED Cannot allocate memory
```

Actual:
```
[1]    306003 IOT instruction (core dumped)  ./a.out
134
```

`abort()` call in `maybe_resize` in `src/unix/core.c` seems to be the culprit. I've a working fix in local and can submit a PR.

Thanks for this amazing library! 



Discussion: While I agree it'd be nicer if libuv didn't abort, I wonder how you'd fix that though? There's no going forward if the watchers list can't grow.
Yes, i return UV_ENOMEM instead of aborting. maybe resize is only called by 2 function IRC.

Creating a new loop shouldn't crash the entire process IMO
For uv_loop_init I of course agree but maybe_resize is also called by uv__io_start and propagating errors from there is likely to be a Sisyphean task. Maybe not literally Sisyphean but close enough to feel that way.
You're right, I didn't looked. It seems to be a lot of work.
Maybe we could start by adding an uv__io_try_start that returns error. uv__io_start will call it and abort on error. This way, ENOMEM aborts can be progressively fixed. Wdyt ?
It needs a little more than that to fix uv_loop_init. My attempt is in #4757.



Fix (patch):
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b391e7c3ca3..73d5aff8926 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -584,6 +584,7 @@ if(LIBUV_BUILD_TESTS)
        test/test-loop-close.c
        test/test-loop-configure.c
        test/test-loop-handles.c
+       test/test-loop-oom.c
        test/test-loop-stop.c
        test/test-loop-time.c
        test/test-metrics.c
diff --git a/Makefile.am b/Makefile.am
index 9b9e6be7178..f3808b696ce 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -206,12 +206,13 @@ test_run_tests_SOURCES = test/blackhole-server.c \
                          test/test-ipc-send-recv.c \
                          test/test-ipc.c \
                          test/test-list.h \
-                         test/test-loop-handles.c \
                          test/test-loop-alive.c \
                          test/test-loop-close.c \
+                         test/test-loop-configure.c \
+                         test/test-loop-handles.c \
+                         test/test-loop-oom.c \
                          test/test-loop-stop.c \
                          test/test-loop-time.c \
-                         test/test-loop-configure.c \
                          test/test-metrics.c \
                          test/test-multiple-listen.c \
                          test/test-mutexes.c \
diff --git a/src/unix/async.c b/src/unix/async.c
index 75a181bc1be..538ae7876f2 100644
--- a/src/unix/async.c
+++ b/src/unix/async.c
@@ -308,8 +308,14 @@ static int uv__async_start(uv_loop_t* loop) {
     return err;
 #endif
 
-  uv__io_init(&loop->async_io_watcher, uv__async_io, pipefd[0]);
-  uv__io_start(loop, &loop->async_io_watcher, POLLIN);
+  err = uv__io_init_start(loop, &loop->async_io_watcher, uv__async_io,
+                          pipefd[0], POLLIN);
+  if (err < 0) {
+    uv__close(pipefd[0]);
+    if (pipefd[1] != -1)
+      uv__close(pipefd[1]);
+    return err;
+  }
   loop->async_wfd = pipefd[1];
 
 #if UV__KQUEUE_EVFILT_USER
diff --git a/src/unix/core.c b/src/unix/core.c
index e64ea81d6ab..115cbe69974 100644
--- a/src/unix/core.c
+++ b/src/unix/core.c
@@ -867,7 +867,7 @@ static unsigned int next_power_of_two(unsigned int val) {
   return val;
 }
 
-static void maybe_resize(uv_loop_t* loop, unsigned int len) {
+static int maybe_resize(uv_loop_t* loop, unsigned int len) {
   uv__io_t** watchers;
   void* fake_watcher_list;
   void* fake_watcher_count;
@@ -875,7 +875,7 @@ static void maybe_resize(uv_loop_t* loop, unsigned int len) {
   unsigned int i;
 
   if (len <= loop->nwatchers)
-    return;
+    return 0;
 
   /* Preserve fake watcher list and count at the end of the watchers */
   if (loop->watchers != NULL) {
@@ -891,7 +891,7 @@ static void maybe_resize(uv_loop_t* loop, unsigned int len) {
                           (nwatchers + 2) * sizeof(loop->watchers[0]));
 
   if (watchers == NULL)
-    abort();
+    return UV_ENOMEM;
   for (i = loop->nwatchers; i < nwatchers; i++)
     watchers[i] = NULL;
   watchers[nwatchers] = fake_watcher_list;
@@ -899,11 +899,11 @@ static void maybe_resize(uv_loop_t* loop, unsigned int len) {
 
   loop->watchers = watchers;
   loop->nwatchers = nwatchers;
+  return 0;
 }
 
 
 void uv__io_init(uv__io_t* w, uv__io_cb cb, int fd) {
-  assert(cb != NULL);
   assert(fd >= -1);
   uv__queue_init(&w->pending_queue);
   uv__queue_init(&w->watcher_queue);
@@ -914,14 +914,18 @@ void uv__io_init(uv__io_t* w, uv__io_cb cb, int fd) {
 }
 
 
-void uv__io_start(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
+int uv__io_start(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
+  int err;
+
   assert(0 == (events & ~(POLLIN | POLLOUT | UV__POLLRDHUP | UV__POLLPRI)));
   assert(0 != events);
   assert(w->fd >= 0);
   assert(w->fd < INT_MAX);
 
   w->pevents |= events;
-  maybe_resize(loop, w->fd + 1);
+  err = maybe_resize(loop, w->fd + 1);
+  if (err)
+    return err;
 
 #if !defined(__sun)
   /* The event ports backend needs to rearm all file descriptors on each and
@@ -929,7 +933,7 @@ void uv__io_start(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
    * short-circuit here if the event mask is unchanged.
    */
   if (w->events == w->pevents)
-    return;
+    return 0;
 #endif
 
   if (uv__queue_empty(&w->watcher_queue))
@@ -939,6 +943,25 @@ void uv__io_start(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
     loop->watchers[w->fd] = w;
     loop->nfds++;
   }
+
+  return 0;
+}
+
+
+int uv__io_init_start(uv_loop_t* loop,
+                      uv__io_t* w,
+                      uv__io_cb cb,
+                      int fd,
+                      unsigned int events) {
+  int err;
+
+  assert(cb != NULL);
+  assert(fd > -1);
+  uv__io_init(w, cb, fd);
+  err = uv__io_start(loop, w, events);
+  if (err)
+    uv__io_init(w, NULL, -1);
+  return err;
 }
 
 
diff --git a/src/unix/internal.h b/src/unix/internal.h
index b1d2b21756d..5002c5fde38 100644
--- a/src/unix/internal.h
+++ b/src/unix/internal.h
@@ -257,7 +257,12 @@ void uv__make_close_pending(uv_handle_t* handle);
 int uv__getiovmax(void);
 
 void uv__io_init(uv__io_t* w, uv__io_cb cb, int fd);
-void uv__io_start(uv_loop_t* loop, uv__io_t* w, unsigned int events);
+int uv__io_start(uv_loop_t* loop, uv__io_t* w, unsigned int events);
+int uv__io_init_start(uv_loop_t* loop,
+                      uv__io_t* w,
+                      uv__io_cb cb,
+                      int fd,
+                      unsigned int events);
 void uv__io_stop(uv_loop_t* loop, uv__io_t* w, unsigned int events);
 void uv__io_close(uv_loop_t* loop, uv__io_t* w);
 void uv__io_feed(uv_loop_t* loop, uv__io_t* w);
diff --git a/src/unix/linux.c b/src/unix/linux.c
index 86c12b4f086..5fa4627da4b 100644
--- a/src/unix/linux.c
+++ b/src/unix/linux.c
@@ -2460,6 +2460,7 @@ static int compare_watchers(const struct watcher_list* a,
 
 
 static int init_inotify(uv_loop_t* loop) {
+  int err;
   int fd;
 
   if (loop->inotify_fd != -1)
@@ -2469,10 +2470,14 @@ static int init_inotify(uv_loop_t* loop) {
   if (fd < 0)
     return UV__ERR(errno);
 
-  loop->inotify_fd = fd;
-  uv__io_init(&loop->inotify_read_watcher, uv__inotify_read, loop->inotify_fd);
-  uv__io_start(loop, &loop->inotify_read_watcher, POLLIN);
+  err = uv__io_init_start(loop, &loop->inotify_read_watcher, uv__inotify_read,
+                          fd, POLLIN);
+  if (err) {
+    uv__close(fd);
+    return err;
+  }
 
+  loop->inotify_fd = fd;
   return 0;
 }
 
diff --git a/src/unix/loop.c b/src/unix/loop.c
index 179ee999d80..5d3f0c7a348 100644
--- a/src/unix/loop.c
+++ b/src/unix/loop.c
@@ -32,12 +32,11 @@ int uv_loop_init(uv_loop_t* loop) {
   void* saved_data;
   int err;
 
-
   saved_data = loop->data;
   memset(loop, 0, sizeof(*loop));
   loop->data = saved_data;
 
-  lfields = (uv__loop_internal_fields_t*) uv__calloc(1, sizeof(*lfields));
+  lfields = uv__calloc(1, sizeof(*lfields));
   if (lfields == NULL)
     return UV_ENOMEM;
   loop->internal_fields = lfields;
@@ -116,6 +115,11 @@ int uv_loop_init(uv_loop_t* loop) {
 fail_signal_init:
   uv__platform_loop_delete(loop);
 
+  if (loop->backend_fd != -1) {
+    uv__close(loop->backend_fd);
+    loop->backend_fd = -1;
+  }
+
 fail_platform_init:
   uv_mutex_destroy(&lfields->loop_metrics.lock);
 
diff --git a/src/unix/signal.c b/src/unix/signal.c
index f23c887d0d6..ccaa72db457 100644
--- a/src/unix/signal.c
+++ b/src/unix/signal.c
@@ -259,22 +259,28 @@ static void uv__signal_unregister_handler(int signum) {
 
 
 static int uv__signal_loop_once_init(uv_loop_t* loop) {
+  int* pipefd;
   int err;
 
   /* Return if already initialized. */
-  if (loop->signal_pipefd[0] != -1)
+  pipefd = loop->signal_pipefd;
+  if (pipefd[0] != -1)
     return 0;
 
-  err = uv__make_pipe(loop->signal_pipefd, UV_NONBLOCK_PIPE);
+  err = uv__make_pipe(pipefd, UV_NONBLOCK_PIPE);
   if (err)
     return err;
 
-  uv__io_init(&loop->signal_io_watcher,
-              uv__signal_event,
-              loop->signal_pipefd[0]);
-  uv__io_start(loop, &loop->signal_io_watcher, POLLIN);
+  err = uv__io_init_start(loop, &loop->signal_io_watcher, uv__signal_event,
+                          pipefd[0], POLLIN);
+  if (err) {
+    uv__close(pipefd[0]);
+    uv__close(pipefd[1]);
+    pipefd[0] = -1;
+    pipefd[1] = -1;
+  }
 
-  return 0;
+  return err;
 }
 
 
diff --git a/src/win/core.c b/src/win/core.c
index bc63b06673a..5f41c87ad5e 100644
--- a/src/win/core.c
+++ b/src/win/core.c
@@ -114,7 +114,7 @@ static int uv__loops_add(uv_loop_t* loop) {
 
 failed_loops_realloc:
   uv_mutex_unlock(&uv__loops_lock);
-  return ERROR_OUTOFMEMORY;
+  return UV_ENOMEM;
 }