ucode: add pending ubus patches
authorFelix Fietkau <nbd@nbd.name>
Fri, 7 Mar 2025 15:17:27 +0000 (16:17 +0100)
committerFelix Fietkau <nbd@nbd.name>
Sun, 9 Mar 2025 15:44:52 +0000 (16:44 +0100)
Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/utils/ucode/patches/100-ubus-add-channel-defer-method.patch [new file with mode: 0644]
package/utils/ucode/patches/101-ubus-add-no_return-argument-to-call-and-channel-requ.patch [new file with mode: 0644]
package/utils/ucode/patches/102-ubus-add-separate-data-callback-for-ubus-defer-calls.patch [new file with mode: 0644]
package/utils/ucode/patches/103-ubus-support-sending-multiple-reply-messages-on-a-re.patch [new file with mode: 0644]
package/utils/ucode/patches/104-ubus-fix-potential-use-after-free-on-channel-disconn.patch [new file with mode: 0644]
package/utils/ucode/patches/105-ubus-remove-broken-implied-await-when-calling-defer-.patch [new file with mode: 0644]

diff --git a/package/utils/ucode/patches/100-ubus-add-channel-defer-method.patch b/package/utils/ucode/patches/100-ubus-add-channel-defer-method.patch
new file mode 100644 (file)
index 0000000..7c5aa81
--- /dev/null
@@ -0,0 +1,155 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 4 Mar 2025 12:01:09 +0100
+Subject: [PATCH] ubus: add channel defer() method
+
+Works in the same way as regular defer calls, except that it operates on
+channels.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/lib/ubus.c
++++ b/lib/ubus.c
+@@ -878,44 +878,30 @@ uc_ubus_chan_request(uc_vm_t *vm, size_t
+       ok_return(res.res);
+ }
+-static uc_value_t *
+-uc_ubus_defer(uc_vm_t *vm, size_t nargs)
++static int
++uc_ubus_defer_common(uc_vm_t *vm, uc_ubus_connection_t *c, uc_ubus_call_res_t *res,
++                     uint32_t id, uc_value_t *funname, uc_value_t *funargs,
++                     uc_value_t *fd, uc_value_t *fdcb, uc_value_t *replycb)
+ {
+-      uc_value_t *objname, *funname, *funargs, *replycb, *fd, *fdcb, *conn, *res = NULL;
+       uc_ubus_deferred_t *defer;
+-      uc_ubus_connection_t *c;
+       enum ubus_msg_status rv;
+       uc_callframe_t *frame;
+-      uint32_t id;
++      uc_value_t *conn;
+       int fd_val = -1;
+-      conn_get(vm, &c);
+-
+-      args_get_named(vm, nargs,
+-                     "object", UC_STRING, REQUIRED, &objname,
+-                     "method", UC_STRING, REQUIRED, &funname,
+-                     "data", UC_OBJECT, OPTIONAL, &funargs,
+-                     "cb", UC_CLOSURE, OPTIONAL, &replycb,
+-                     "fd", 0, NAMED, &fd,
+-                     "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+-
+       blob_buf_init(&c->buf, 0);
+       if (funargs)
+               ucv_object_to_blob(funargs, &c->buf);
+-
+       if (fd) {
+               fd_val = get_fd(vm, fd);
+-              if (fd_val < 0)
+-                      err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid file descriptor argument");
++              if (fd_val < 0) {
++                      rv = UBUS_STATUS_INVALID_ARGUMENT;
++                      set_error(rv, "Invalid file descriptor argument");
++                      return rv;
++              }
+       }
+-      rv = ubus_lookup_id(&c->ctx, ucv_string_get(objname), &id);
+-
+-      if (rv != UBUS_STATUS_OK)
+-              err_return(rv, "Failed to resolve object name '%s'",
+-                         ucv_string_get(objname));
+-
+       defer = xalloc(sizeof(*defer));
+       rv = ubus_invoke_async_fd(&c->ctx, id, ucv_string_get(funname),
+@@ -935,11 +921,11 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs)
+               defer->timeout.cb = uc_ubus_call_timeout_cb;
+               uloop_timeout_set(&defer->timeout, c->timeout * 1000);
+-              res = uc_resource_new(defer_type, defer);
++              res->res = uc_resource_new(defer_type, defer);
+               frame = uc_vector_last(&vm->callframes);
+               conn = frame ? frame->ctx : NULL;
+-              defer->registry_index = request_reg_add(vm, ucv_get(res), ucv_get(replycb), ucv_get(fdcb), ucv_get(conn), ucv_get(fd));
++              defer->registry_index = request_reg_add(vm, ucv_get(res->res), ucv_get(replycb), ucv_get(fdcb), ucv_get(conn), ucv_get(fd));
+               if (!uc_ubus_have_uloop()) {
+                       have_own_uloop = true;
+@@ -958,11 +944,64 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs)
+               free(defer);
+       }
++      return rv;
++}
++
++static uc_value_t *
++uc_ubus_defer(uc_vm_t *vm, size_t nargs)
++{
++      uc_value_t *objname, *funname, *funargs, *replycb, *fd, *fdcb = NULL;
++      uc_ubus_call_res_t res = { 0 };
++      uc_ubus_connection_t *c;
++      uint32_t id;
++      int rv;
++
++      conn_get(vm, &c);
++
++      rv = ubus_lookup_id(&c->ctx, ucv_string_get(objname), &id);
++      if (rv != UBUS_STATUS_OK)
++              err_return(rv, "Failed to resolve object name '%s'",
++                         ucv_string_get(objname));
++
++      args_get_named(vm, nargs,
++                     "object", UC_STRING, REQUIRED, &objname,
++                     "method", UC_STRING, REQUIRED, &funname,
++                     "data", UC_OBJECT, OPTIONAL, &funargs,
++                     "cb", UC_CLOSURE, OPTIONAL, &replycb,
++                     "fd", 0, NAMED, &fd,
++                     "fd_cb", UC_CLOSURE, NAMED, &fdcb);
++
++      rv = uc_ubus_defer_common(vm, c, &res, id, funname, funargs, fd, fdcb, replycb);
+       if (rv != UBUS_STATUS_OK)
+               err_return(rv, "Failed to invoke function '%s' on object '%s'",
+                          ucv_string_get(funname), ucv_string_get(objname));
+-      ok_return(res);
++      ok_return(res.res);
++}
++
++static uc_value_t *
++uc_ubus_chan_defer(uc_vm_t *vm, size_t nargs)
++{
++      uc_value_t *funname, *funargs, *replycb, *fd, *fdcb = NULL;
++      uc_ubus_call_res_t res = { 0 };
++      uc_ubus_connection_t *c;
++      int rv;
++
++      conn_get(vm, &c);
++
++      args_get_named(vm, nargs,
++                     "method", UC_STRING, REQUIRED, &funname,
++                     "data", UC_OBJECT, OPTIONAL, &funargs,
++                     "cb", UC_CLOSURE, OPTIONAL, &replycb,
++                     "fd", 0, NAMED, &fd,
++                     "fd_cb", UC_CLOSURE, NAMED, &fdcb);
++
++      rv = uc_ubus_defer_common(vm, c, &res, 0, funname, funargs, fd, fdcb, replycb);
++      if (rv != UBUS_STATUS_OK)
++              err_return(rv, "Failed to invoke function '%s' on channel",
++                         ucv_string_get(funname));
++
++      ok_return(res.res);
+ }
+@@ -2384,6 +2423,7 @@ static const uc_function_list_t conn_fns
+ static const uc_function_list_t chan_fns[] = {
+       { "request",            uc_ubus_chan_request },
++      { "defer",                      uc_ubus_chan_defer },
+       { "error",                      uc_ubus_error },
+       { "disconnect",         uc_ubus_disconnect },
+ };
diff --git a/package/utils/ucode/patches/101-ubus-add-no_return-argument-to-call-and-channel-requ.patch b/package/utils/ucode/patches/101-ubus-add-no_return-argument-to-call-and-channel-requ.patch
new file mode 100644 (file)
index 0000000..0f653d2
--- /dev/null
@@ -0,0 +1,88 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 4 Mar 2025 13:17:49 +0100
+Subject: [PATCH] ubus: add no_return argument to call and channel request
+
+Allows issuing a call without waiting for any result. This is useful
+for implementing non-blocking notification calls without the overhead
+of extra request tracking.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/lib/ubus.c
++++ b/lib/ubus.c
+@@ -764,7 +764,8 @@ get_fd(uc_vm_t *vm, uc_value_t *val)
+ static int
+ uc_ubus_call_common(uc_vm_t *vm, uc_ubus_connection_t *c, uc_ubus_call_res_t *res,
+                     uint32_t id, uc_value_t *funname, uc_value_t *funargs,
+-                    uc_value_t *fd, uc_value_t *fdcb, uc_value_t *mret)
++                    uc_value_t *fd, uc_value_t *fdcb, uc_value_t *noret,
++                    uc_value_t *mret)
+ {
+       uc_ubus_deferred_t defer = {};
+       enum ubus_msg_status rv;
+@@ -796,8 +797,12 @@ uc_ubus_call_common(uc_vm_t *vm, uc_ubus
+               defer.registry_index = request_reg_add(vm, NULL, NULL, ucv_get(fdcb), NULL, NULL);
+       }
+-      if (rv == UBUS_STATUS_OK)
+-              rv = ubus_complete_request(&c->ctx, &defer.request, c->timeout * 1000);
++      if (rv == UBUS_STATUS_OK) {
++              if (ucv_is_truish(noret))
++                      ubus_abort_request(&c->ctx, &defer.request);
++              else
++                      rv = ubus_complete_request(&c->ctx, &defer.request, c->timeout * 1000);
++      }
+       if (defer.request.fd_cb)
+               request_reg_clear(vm, defer.registry_index);
+@@ -808,7 +813,7 @@ uc_ubus_call_common(uc_vm_t *vm, uc_ubus
+ static uc_value_t *
+ uc_ubus_call(uc_vm_t *vm, size_t nargs)
+ {
+-      uc_value_t *obj, *funname, *funargs, *fd, *fdcb, *mret = NULL;
++      uc_value_t *obj, *funname, *funargs, *fd, *fdcb, *noret, *mret = NULL;
+       uc_ubus_call_res_t res = { 0 };
+       uc_ubus_connection_t *c;
+       enum ubus_msg_status rv;
+@@ -819,6 +824,7 @@ uc_ubus_call(uc_vm_t *vm, size_t nargs)
+                      "method", UC_STRING, REQUIRED, &funname,
+                      "data", UC_OBJECT, OPTIONAL, &funargs,
+                      "multiple_return", UC_BOOLEAN, OPTIONAL, &mret,
++                     "no_return", UC_BOOLEAN, NAMED, &noret,
+                      "fd", 0, NAMED, &fd,
+                      "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+@@ -839,7 +845,7 @@ uc_ubus_call(uc_vm_t *vm, size_t nargs)
+                          "Argument object is not string or integer");
+       }
+-      rv = uc_ubus_call_common(vm, c, &res, id, funname, funargs, fd, fdcb, mret);
++      rv = uc_ubus_call_common(vm, c, &res, id, funname, funargs, fd, fdcb, noret, mret);
+       if (rv != UBUS_STATUS_OK) {
+               if (ucv_type(obj) == UC_STRING)
+@@ -856,7 +862,7 @@ uc_ubus_call(uc_vm_t *vm, size_t nargs)
+ static uc_value_t *
+ uc_ubus_chan_request(uc_vm_t *vm, size_t nargs)
+ {
+-      uc_value_t *funname, *funargs, *fd, *fdcb, *mret = NULL;
++      uc_value_t *funname, *funargs, *fd, *fdcb, *noret, *mret = NULL;
+       uc_ubus_call_res_t res = { 0 };
+       uc_ubus_connection_t *c;
+       enum ubus_msg_status rv;
+@@ -865,12 +871,13 @@ uc_ubus_chan_request(uc_vm_t *vm, size_t
+                      "method", UC_STRING, REQUIRED, &funname,
+                      "data", UC_OBJECT, OPTIONAL, &funargs,
+                      "multiple_return", UC_BOOLEAN, OPTIONAL, &mret,
++                     "no_return", UC_BOOLEAN, NAMED, &noret,
+                      "fd", 0, NAMED, &fd,
+                      "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+       conn_get(vm, &c);
+-      rv = uc_ubus_call_common(vm, c, &res, 0, funname, funargs, fd, fdcb, mret);
++      rv = uc_ubus_call_common(vm, c, &res, 0, funname, funargs, fd, fdcb, noret, mret);
+       if (rv != UBUS_STATUS_OK)
+               err_return(rv, "Failed to send request '%s' on channel",
+                          ucv_string_get(funname));
diff --git a/package/utils/ucode/patches/102-ubus-add-separate-data-callback-for-ubus-defer-calls.patch b/package/utils/ucode/patches/102-ubus-add-separate-data-callback-for-ubus-defer-calls.patch
new file mode 100644 (file)
index 0000000..a4e2514
--- /dev/null
@@ -0,0 +1,171 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 4 Mar 2025 14:32:25 +0100
+Subject: [PATCH] ubus: add separate data callback for ubus defer calls
+
+This allows receiving one call per data message, which makes it possible to
+properly deal with multiple response messages.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/lib/ubus.c
++++ b/lib/ubus.c
+@@ -306,14 +306,14 @@ _uc_reg_clear(uc_vm_t *vm, const char *k
+       _uc_reg_clear(vm, "ubus.connections", idx, 4)
+-#define request_reg_add(vm, request, cb, fdcb, conn, fd) \
+-      _uc_reg_add(vm, "ubus.requests", 5, request, cb, fdcb, conn, fd)
++#define request_reg_add(vm, request, cb, datacb, fdcb, conn, fd) \
++      _uc_reg_add(vm, "ubus.requests", 6, request, cb, datacb, fdcb, conn, fd)
+-#define request_reg_get(vm, idx, request, cb, fdcb) \
+-      _uc_reg_get(vm, "ubus.requests", idx, 3, request, cb, fdcb)
++#define request_reg_get(vm, idx, request, cb, datacb, fdcb) \
++      _uc_reg_get(vm, "ubus.requests", idx, 4, request, cb, datacb, fdcb)
+ #define request_reg_clear(vm, idx) \
+-      _uc_reg_clear(vm, "ubus.requests", idx, 5)
++      _uc_reg_clear(vm, "ubus.requests", idx, 6)
+ #define object_reg_add(vm, obj, msg, cb) \
+@@ -636,7 +636,7 @@ uc_ubus_call_user_cb(uc_ubus_deferred_t
+ {
+       uc_value_t *this, *func;
+-      request_reg_get(defer->vm, defer->registry_index, &this, &func, NULL);
++      request_reg_get(defer->vm, defer->registry_index, &this, &func, NULL, NULL);
+       if (ucv_is_callable(func)) {
+               uc_vm_stack_push(defer->vm, ucv_get(this));
+@@ -666,6 +666,26 @@ uc_ubus_call_data_cb(struct ubus_request
+ }
+ static void
++uc_ubus_call_data_user_cb(struct ubus_request *req, int type, struct blob_attr *msg)
++{
++      uc_ubus_deferred_t *defer = container_of(req, uc_ubus_deferred_t, request);
++      uc_value_t *this, *func, *reply;
++
++      request_reg_get(defer->vm, defer->registry_index, &this, NULL, &func, NULL);
++
++      if (ucv_is_callable(func)) {
++              reply = blob_array_to_ucv(defer->vm, blob_data(msg), blob_len(msg), true);
++
++              uc_vm_stack_push(defer->vm, ucv_get(this));
++              uc_vm_stack_push(defer->vm, ucv_get(func));
++              uc_vm_stack_push(defer->vm, ucv_get(reply));
++
++              if (uc_vm_call(defer->vm, true, 1) == EXCEPTION_NONE)
++                      ucv_put(uc_vm_stack_pop(defer->vm));
++      }
++}
++
++static void
+ uc_ubus_call_fd_cb(struct ubus_request *req, int fd)
+ {
+       uc_ubus_deferred_t *defer = container_of(req, uc_ubus_deferred_t, request);
+@@ -674,7 +694,7 @@ uc_ubus_call_fd_cb(struct ubus_request *
+       if (defer->complete)
+               return;
+-      request_reg_get(defer->vm, defer->registry_index, &this, NULL, &func);
++      request_reg_get(defer->vm, defer->registry_index, &this, NULL, NULL, &func);
+       if (ucv_is_callable(func)) {
+               uc_vm_stack_push(defer->vm, ucv_get(this));
+               uc_vm_stack_push(defer->vm, ucv_get(func));
+@@ -794,7 +814,7 @@ uc_ubus_call_common(uc_vm_t *vm, uc_ubus
+       defer.request.priv = res;
+       if (ucv_is_callable(fdcb)) {
+               defer.request.fd_cb = uc_ubus_call_fd_cb;
+-              defer.registry_index = request_reg_add(vm, NULL, NULL, ucv_get(fdcb), NULL, NULL);
++              defer.registry_index = request_reg_add(vm, NULL, NULL, NULL, ucv_get(fdcb), NULL, NULL);
+       }
+       if (rv == UBUS_STATUS_OK) {
+@@ -888,7 +908,8 @@ uc_ubus_chan_request(uc_vm_t *vm, size_t
+ static int
+ uc_ubus_defer_common(uc_vm_t *vm, uc_ubus_connection_t *c, uc_ubus_call_res_t *res,
+                      uint32_t id, uc_value_t *funname, uc_value_t *funargs,
+-                     uc_value_t *fd, uc_value_t *fdcb, uc_value_t *replycb)
++                     uc_value_t *fd, uc_value_t *fdcb, uc_value_t *replycb,
++                     uc_value_t *datacb)
+ {
+       uc_ubus_deferred_t *defer;
+       enum ubus_msg_status rv;
+@@ -918,7 +939,10 @@ uc_ubus_defer_common(uc_vm_t *vm, uc_ubu
+               defer->vm = vm;
+               defer->ctx = &c->ctx;
+-              defer->request.data_cb = uc_ubus_call_data_cb;
++              if (ucv_is_callable(datacb))
++                      defer->request.data_cb = uc_ubus_call_data_user_cb;
++              else
++                      defer->request.data_cb = uc_ubus_call_data_cb;
+               if (ucv_is_callable(fdcb))
+                       defer->request.fd_cb = uc_ubus_call_fd_cb;
+               defer->request.complete_cb = uc_ubus_call_done_cb;
+@@ -932,7 +956,8 @@ uc_ubus_defer_common(uc_vm_t *vm, uc_ubu
+               frame = uc_vector_last(&vm->callframes);
+               conn = frame ? frame->ctx : NULL;
+-              defer->registry_index = request_reg_add(vm, ucv_get(res->res), ucv_get(replycb), ucv_get(fdcb), ucv_get(conn), ucv_get(fd));
++              defer->registry_index = request_reg_add(vm, ucv_get(res->res), ucv_get(replycb), ucv_get(datacb),
++                                                      ucv_get(fdcb), ucv_get(conn), ucv_get(fd));
+               if (!uc_ubus_have_uloop()) {
+                       have_own_uloop = true;
+@@ -957,7 +982,7 @@ uc_ubus_defer_common(uc_vm_t *vm, uc_ubu
+ static uc_value_t *
+ uc_ubus_defer(uc_vm_t *vm, size_t nargs)
+ {
+-      uc_value_t *objname, *funname, *funargs, *replycb, *fd, *fdcb = NULL;
++      uc_value_t *objname, *funname, *funargs, *replycb, *datacb, *fd, *fdcb = NULL;
+       uc_ubus_call_res_t res = { 0 };
+       uc_ubus_connection_t *c;
+       uint32_t id;
+@@ -975,10 +1000,11 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs)
+                      "method", UC_STRING, REQUIRED, &funname,
+                      "data", UC_OBJECT, OPTIONAL, &funargs,
+                      "cb", UC_CLOSURE, OPTIONAL, &replycb,
++                     "data_cb", UC_CLOSURE, OPTIONAL, &datacb,
+                      "fd", 0, NAMED, &fd,
+                      "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+-      rv = uc_ubus_defer_common(vm, c, &res, id, funname, funargs, fd, fdcb, replycb);
++      rv = uc_ubus_defer_common(vm, c, &res, id, funname, funargs, fd, fdcb, replycb, datacb);
+       if (rv != UBUS_STATUS_OK)
+               err_return(rv, "Failed to invoke function '%s' on object '%s'",
+                          ucv_string_get(funname), ucv_string_get(objname));
+@@ -989,7 +1015,7 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs)
+ static uc_value_t *
+ uc_ubus_chan_defer(uc_vm_t *vm, size_t nargs)
+ {
+-      uc_value_t *funname, *funargs, *replycb, *fd, *fdcb = NULL;
++      uc_value_t *funname, *funargs, *replycb, *datacb, *fd, *fdcb = NULL;
+       uc_ubus_call_res_t res = { 0 };
+       uc_ubus_connection_t *c;
+       int rv;
+@@ -1000,10 +1026,11 @@ uc_ubus_chan_defer(uc_vm_t *vm, size_t n
+                      "method", UC_STRING, REQUIRED, &funname,
+                      "data", UC_OBJECT, OPTIONAL, &funargs,
+                      "cb", UC_CLOSURE, OPTIONAL, &replycb,
++                     "data_cb", UC_CLOSURE, OPTIONAL, &datacb,
+                      "fd", 0, NAMED, &fd,
+                      "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+-      rv = uc_ubus_defer_common(vm, c, &res, 0, funname, funargs, fd, fdcb, replycb);
++      rv = uc_ubus_defer_common(vm, c, &res, 0, funname, funargs, fd, fdcb, replycb, datacb);
+       if (rv != UBUS_STATUS_OK)
+               err_return(rv, "Failed to invoke function '%s' on channel",
+                          ucv_string_get(funname));
+@@ -1520,7 +1547,7 @@ uc_ubus_handle_reply_common(struct ubus_
+                       /* Add wrapped request context into registry to prevent GC'ing
+                        * until reply or timeout occurred */
+-                      callctx->registry_index = request_reg_add(vm, ucv_get(reqobj), NULL, NULL, NULL, NULL);
++                      callctx->registry_index = request_reg_add(vm, ucv_get(reqobj), NULL, NULL, NULL, NULL, NULL);
+               }
+               /* Otherwise, when the function returned an object, treat it as
diff --git a/package/utils/ucode/patches/103-ubus-support-sending-multiple-reply-messages-on-a-re.patch b/package/utils/ucode/patches/103-ubus-support-sending-multiple-reply-messages-on-a-re.patch
new file mode 100644 (file)
index 0000000..283c6ae
--- /dev/null
@@ -0,0 +1,110 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 4 Mar 2025 14:47:46 +0100
+Subject: [PATCH] ubus: support sending multiple reply messages on a
+ request
+
+When passing true in the return code argument, assume that more reply calls
+are coming and only send a reply data message without the status code.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/lib/ubus.c
++++ b/lib/ubus.c
+@@ -1058,16 +1058,21 @@ uc_ubus_request_finish_common(uc_ubus_re
+ }
+ static void
+-uc_ubus_request_finish(uc_ubus_request_t *callctx, int code, uc_value_t *reply)
++uc_ubus_request_send_reply(uc_ubus_request_t *callctx, uc_value_t *reply)
+ {
+-      if (callctx->replied)
++      if (!reply)
+               return;
+-      if (reply) {
+-              blob_buf_init(&buf, 0);
+-              ucv_object_to_blob(reply, &buf);
+-              ubus_send_reply(callctx->ctx, &callctx->req, buf.head);
+-      }
++      blob_buf_init(&buf, 0);
++      ucv_object_to_blob(reply, &buf);
++      ubus_send_reply(callctx->ctx, &callctx->req, buf.head);
++}
++
++static void
++uc_ubus_request_finish(uc_ubus_request_t *callctx, int code)
++{
++      if (callctx->replied)
++              return;
+       uc_ubus_request_finish_common(callctx, code);
+       request_reg_clear(callctx->vm, callctx->registry_index);
+@@ -1078,7 +1083,7 @@ uc_ubus_request_timeout(struct uloop_tim
+ {
+       uc_ubus_request_t *callctx = container_of(timeout, uc_ubus_request_t, timeout);
+-      uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT, NULL);
++      uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT);
+ }
+ static uc_value_t *
+@@ -1087,25 +1092,36 @@ uc_ubus_request_reply(uc_vm_t *vm, size_
+       uc_ubus_request_t **callctx = uc_fn_this("ubus.request");
+       int64_t code = UBUS_STATUS_OK;
+       uc_value_t *reply, *rcode;
++      bool more = false;
+       if (!callctx || !*callctx)
+               err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid call context");
+       args_get(vm, nargs,
+-               "reply", UC_OBJECT, true, &reply,
+-               "rcode", UC_INTEGER, true, &rcode);
++               "reply", UC_OBJECT, OPTIONAL, &reply,
++               "rcode", 0, OPTIONAL, &rcode);
+       if ((*callctx)->replied)
+               err_return(UBUS_STATUS_INVALID_ARGUMENT, "Reply has already been sent");
+-      if (rcode) {
++      switch (ucv_type(rcode)) {
++      case UC_INTEGER:
+               code = ucv_int64_get(rcode);
+               if (errno == ERANGE || code < 0 || code > __UBUS_STATUS_LAST)
+                       code = UBUS_STATUS_UNKNOWN_ERROR;
++              break;
++      case UC_BOOLEAN:
++      case UC_NULL:
++              more = ucv_is_truish(rcode);
++              break;
++      default:
++              err_return(UBUS_STATUS_INVALID_ARGUMENT, "Argument is not integer or bool");
+       }
+-      uc_ubus_request_finish(*callctx, code, reply);
++      uc_ubus_request_send_reply(*callctx, reply);
++      if (!more)
++              uc_ubus_request_finish(*callctx, code);
+       ok_return(ucv_boolean_new(true));
+ }
+@@ -1171,7 +1187,7 @@ uc_ubus_request_error(uc_vm_t *vm, size_
+       if (errno == ERANGE || code < 0 || code > __UBUS_STATUS_LAST)
+               code = UBUS_STATUS_UNKNOWN_ERROR;
+-      uc_ubus_request_finish(*callctx, code, NULL);
++      uc_ubus_request_finish(*callctx, code);
+       ok_return(ucv_boolean_new(true));
+ }
+@@ -2538,7 +2554,7 @@ static void free_object(void *ud) {
+ static void free_request(void *ud) {
+       uc_ubus_request_t *callctx = ud;
+-      uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT, NULL);
++      uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT);
+       uloop_timeout_cancel(&callctx->timeout);
+       free(callctx);
+ }
diff --git a/package/utils/ucode/patches/104-ubus-fix-potential-use-after-free-on-channel-disconn.patch b/package/utils/ucode/patches/104-ubus-fix-potential-use-after-free-on-channel-disconn.patch
new file mode 100644 (file)
index 0000000..7e30d17
--- /dev/null
@@ -0,0 +1,28 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 4 Mar 2025 20:55:43 +0100
+Subject: [PATCH] ubus: fix potential use-after-free on channel disconnect
+
+Ensure that free function only gets called called after shutdown,
+because it potentially invalidates the uc_ubus_connection_t
+pointer.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/lib/ubus.c
++++ b/lib/ubus.c
+@@ -2358,12 +2358,12 @@ uc_ubus_channel_disconnect_cb(struct ubu
+       }
+       blob_buf_free(&c->buf);
+-      if (c->registry_index >= 0)
+-              connection_reg_clear(c->vm, c->registry_index);
+       if (c->ctx.sock.fd >= 0) {
+               ubus_shutdown(&c->ctx);
+               c->ctx.sock.fd = -1;
+       }
++      if (c->registry_index >= 0)
++              connection_reg_clear(c->vm, c->registry_index);
+ }
+ static uc_value_t *
diff --git a/package/utils/ucode/patches/105-ubus-remove-broken-implied-await-when-calling-defer-.patch b/package/utils/ucode/patches/105-ubus-remove-broken-implied-await-when-calling-defer-.patch
new file mode 100644 (file)
index 0000000..d81952e
--- /dev/null
@@ -0,0 +1,105 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 7 Mar 2025 14:33:10 +0100
+Subject: [PATCH] ubus: remove broken implied await when calling defer()
+ outside of uloop.run()
+
+Calling ubus.defer() outside of uloop.run() was apparently broken since commit
+1cb04f9b76e2 ("ubus: add object publishing, notify and subscribe support") from
+March 2022.
+
+It was supposed to block until the request completes, however it blocked forever
+due to a counter imbalance introduced by the above commit.
+
+These days this 'feature' is of questionable value, since req.await() exists,
+and there is a legitimate use for issuing deferred requests from outside of
+uloop.run() and then calling uloop.run() or req.await() afterwards.
+
+Since nobody noticed the breakage in all this time, let's just get rid of this
+to get rid of some API footguns.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -180,7 +180,6 @@ if(UBUS_SUPPORT)
+   try_compile(HAVE_NEW_UBUS_STATUS_CODES
+     ${CMAKE_BINARY_DIR}
+     "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/test.c")
+-  check_symbol_exists(uloop_fd_set_cb "libubox/uloop.h" FD_SET_CB_EXISTS)
+   check_function_exists(uloop_timeout_remaining64 REMAINING64_FUNCTION_EXISTS)
+   check_function_exists(ubus_channel_connect HAVE_CHANNEL_SUPPORT)
+   if(REMAINING64_FUNCTION_EXISTS)
+--- a/lib/ubus.c
++++ b/lib/ubus.c
+@@ -132,9 +132,6 @@ static uc_resource_type_t *defer_type;
+ static uc_resource_type_t *conn_type;
+ static uc_resource_type_t *chan_type;
+-static uint64_t n_cb_active;
+-static bool have_own_uloop;
+-
+ static struct blob_buf buf;
+ typedef struct {
+@@ -649,11 +646,6 @@ uc_ubus_call_user_cb(uc_ubus_deferred_t
+       }
+       request_reg_clear(defer->vm, defer->registry_index);
+-
+-      n_cb_active--;
+-
+-      if (have_own_uloop && n_cb_active == 0)
+-              uloop_end();
+ }
+ static void
+@@ -735,24 +727,6 @@ uc_ubus_call_timeout_cb(struct uloop_tim
+       uc_ubus_call_user_cb(defer, UBUS_STATUS_TIMEOUT, NULL);
+ }
+-static bool
+-uc_ubus_have_uloop(void)
+-{
+-      bool prev = uloop_cancelled;
+-      bool active;
+-
+-#ifdef HAVE_ULOOP_FD_SET_CB
+-      if (uloop_fd_set_cb)
+-              return true;
+-#endif
+-
+-      uloop_cancelled = true;
+-      active = uloop_cancelling();
+-      uloop_cancelled = prev;
+-
+-      return active;
+-}
+-
+ static int
+ get_fd(uc_vm_t *vm, uc_value_t *val)
+ {
+@@ -958,11 +932,6 @@ uc_ubus_defer_common(uc_vm_t *vm, uc_ubu
+               defer->registry_index = request_reg_add(vm, ucv_get(res->res), ucv_get(replycb), ucv_get(datacb),
+                                                       ucv_get(fdcb), ucv_get(conn), ucv_get(fd));
+-
+-              if (!uc_ubus_have_uloop()) {
+-                      have_own_uloop = true;
+-                      uloop_run();
+-              }
+       }
+       else {
+               uc_vm_stack_push(vm, ucv_get(replycb));
+@@ -2303,11 +2272,6 @@ uc_ubus_defer_abort(uc_vm_t *vm, size_t
+       request_reg_clear((*d)->vm, (*d)->registry_index);
+-      n_cb_active--;
+-
+-      if (have_own_uloop && n_cb_active == 0)
+-              uloop_end();
+-
+       (*d)->complete = true;
+       ok_return(ucv_boolean_new(true));