<feed xmlns='http://www.w3.org/2005/Atom'>
<title>libubox, branch master</title>
<subtitle>C utility functions for OpenWrt</subtitle>
<id>https://git-03.infra.openwrt.org/project/libubox/atom?h=master</id>
<link rel='self' href='https://git-03.infra.openwrt.org/project/libubox/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/libubox/'/>
<updated>2026-10-08T11:19:54Z</updated>
<entry>
<title>uloop: run kqueue interval callbacks after the events are collected</title>
<updated>2026-10-08T11:19:54Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-10-08T11:19:54Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/libubox/commit/?id=da2d205a5cf3e877c45a2eec488218cbaf452e7d'/>
<id>urn:sha1:da2d205a5cf3e877c45a2eec488218cbaf452e7d</id>
<content type='text'>
uloop_fetch_events() invoked an interval callback while it still collected
the events of the batch, and before uloop_run_events() published them in
cur_nfds. If the callback deleted a descriptor whose event was already
collected, uloop_fd_delete() did not drop the event, and uloop invoked the
callback of the deleted descriptor. If the callback cancelled another
interval that fired in the same batch, uloop invoked that interval too,
possibly after its owner freed it.

Collect the interval events first, publish the descriptor events, and then
run the interval callbacks. timer_remove() drops the pending events of a
cancelled interval. As an interval callback may nest uloop_run(), keep the
pending batches on a stack and return the number of descriptor events that
are left.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>uloop: report a full socket hangup on kqueue as an error</title>
<updated>2026-10-08T11:18:46Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-10-08T11:18:46Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/libubox/commit/?id=531dbb03cbfaefbbb77a6b93f215ceee52d574b9'/>
<id>urn:sha1:531dbb03cbfaefbbb77a6b93f215ceee52d574b9</id>
<content type='text'>
epoll reports EPOLLHUP for a socket once both directions are shut down,
for example when the peer of a unix stream socket closes it, or when the
peer of a TCP connection closes after the local side shut down writing.
uloop then sets the error flag and removes the descriptor. On kqueue,
EV_EOF on a socket without a pending error only set the eof flag, so the
descriptor stayed registered.

getpeername() fails once both directions of a socket are shut down, and
succeeds while only the peer stopped sending. Use it to tell EPOLLHUP from
EPOLLRDHUP.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>uloop: fix edge trigger on kqueue</title>
<updated>2026-10-08T10:30:18Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-10-08T10:29:30Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/libubox/commit/?id=ff8db08443b0928e6b5ad477aecbdb1322a011b6'/>
<id>urn:sha1:ff8db08443b0928e6b5ad477aecbdb1322a011b6</id>
<content type='text'>
Commit 17f4e41ecb80 ("uloop: improve edge trigger reliability on mac os x")
registered an edge-triggered descriptor level-triggered first and marked it
with ULOOP_EDGE_DEFER, to switch it to EV_CLEAR after the first event.
Since commit 75a3b870cace ("uloop: add support for integrating with a
different event loop"), uloop_fd_add() stores the flags of the caller in
fd-&gt;flags, which drops ULOOP_EDGE_DEFER. The switch never runs, and
ULOOP_EDGE_TRIGGER behaves like level trigger.

The switch also cannot work: EV_ADD on an existing filter does not change
its EV_CLEAR state. The filter must be deleted and added again.

Current macOS reports pending data once when a filter is added with
EV_CLEAR, so the deferral is not necessary. Remove ULOOP_EDGE_DEFER and
register EV_CLEAR directly. If the edge trigger mode changes, delete the
registered filters first.

epoll re-arms an edge-triggered descriptor on every uloop_fd_add(), because
EPOLL_CTL_MOD reports a ready descriptor again. Add the filters of an
edge-triggered descriptor again on every call, which does the same on
kqueue.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>uloop: report a hangup on kqueue as an error, like epoll</title>
<updated>2026-10-08T10:28:43Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-10-08T10:28:43Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/libubox/commit/?id=6b4394f900af120e90521ed52b54aa2afe26958a'/>
<id>urn:sha1:6b4394f900af120e90521ed52b54aa2afe26958a</id>
<content type='text'>
epoll reports a pipe whose other end was closed with EPOLLHUP or EPOLLERR,
and uloop then sets the error flag and removes the descriptor, unless
ULOOP_ERROR_CB is set. kqueue reports the same condition with EV_EOF, which
uloop treated as end of file only. The descriptor stayed registered, and
the level-triggered filter invoked the callback on every loop iteration.
EV_ERROR, the flag that uloop checked, is only returned for changelist
errors and never for a hangup.

Treat EV_EOF as an error if fflags holds a socket error, or if the
descriptor is not a socket. EV_EOF on a socket without an error means that
the peer stopped sending, which epoll reports with EPOLLRDHUP, so it only
sets the eof flag.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>uloop: fill one event entry per descriptor on kqueue</title>
<updated>2026-10-08T10:28:13Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-10-08T10:28:07Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/libubox/commit/?id=f9163076e12d3f99a63a59e7e852ed5f9a82df61'/>
<id>urn:sha1:f9163076e12d3f99a63a59e7e852ed5f9a82df61</id>
<content type='text'>
uloop_fetch_events() stored each kevent at its own index in cur_fds, but
did not write the entry for an interval timer event. uloop_run_events()
then replayed the stale entry from an earlier batch, which invoked a
descriptor callback without an event, or with a pointer to a freed
descriptor.

kqueue also reports read and write readiness as separate events. A
descriptor that is ready for both got two callbacks per batch, and the
second one ran even if uloop had already removed the descriptor.

Fill cur_fds with one entry per descriptor, merge the read and write
events into it, and return the number of entries.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>blobmsg: use flexible-array member in blobmsg_name()</title>
<updated>2026-07-21T08:47:52Z</updated>
<author>
<name>Michael Pfeifroth</name>
</author>
<published>2026-07-21T08:46:12Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/libubox/commit/?id=e7608b69283d919d031d13cc8e21692503f5dbea'/>
<id>urn:sha1:e7608b69283d919d031d13cc8e21692503f5dbea</id>
<content type='text'>
blobmsg_name() returned (const char *)(hdr + 1), i.e. a pointer to
the byte immediately after struct blobmsg_hdr. GCC's stringop
analysis treats that region as size 0, so any caller that passes the
result to strcmp()/strlen()/etc. under -Wall -Wstringop-overread
triggers a false-positive diagnostic. For example, building uhttpd
(which uses -Wall -Werror) on aarch64 glibc with GCC 12.3.0 fails
with:

  client.c:302:22: error: 'strcmp' reading 1 or more bytes from a
      region of size 0 [-Werror=stringop-overread]
    302 |    if (!strcmp(blobmsg_name(cur), "URL"))

Return hdr-&gt;name instead. The flexible array member has unknown
size in GCC's object-size model, so no warning is emitted. The
generated pointer is identical.

Signed-off-by: Michael Pfeifroth &lt;micpf@westermo.com&gt;
</content>
</entry>
<entry>
<title>ustream: allow freeing the stream from within notify callbacks</title>
<updated>2026-07-08T16:38:02Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-07-08T16:38:02Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/libubox/commit/?id=17f527fb6c30bf9073104f03337c2b7c03158bdb'/>
<id>urn:sha1:17f527fb6c30bf9073104f03337c2b7c03158bdb</id>
<content type='text'>
Freeing a ustream from notify_read or notify_write left the core and
the fd implementation operating on freed memory: ustream_fill_read()
clears the pending callback flag after notify_read, the fd read loop
keeps filling the stream, ustream_write_pending() evaluates the EOF
state after notify_write and the poll handler continues with write and
error processing afterwards.

Let ustream_free() signal the innermost active dispatch guard through
a flag pointer stored in the stream. A triggered guard forwards the
signal to the next outer one, so nested dispatch sites all bail out
without touching the freed stream. notify_state is invoked as a tail
call and needs no guard.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>vlist: pass the tree as comparator context in VLIST_TREE_INIT</title>
<updated>2026-07-08T08:47:08Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-07-04T20:12:19Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/libubox/commit/?id=7677b7a4f3a46f68e6f5ba6818f7b72fdd7dbaa0'/>
<id>urn:sha1:7677b7a4f3a46f68e6f5ba6818f7b72fdd7dbaa0</id>
<content type='text'>
vlist_init() initialises the underlying AVL tree with the vlist_tree as
the comparator context pointer, but the static VLIST_TREE_INIT() macro
passed NULL. A comparator that relies on the vlist convention of
receiving the tree as its third argument therefore worked for trees set
up with vlist_init() but received NULL for trees defined statically with
VLIST_TREE()/VLIST_TREE_INIT(), causing a crash or miscompare. Pass the
tree so both initialisation paths behave identically.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>uloop: fix use-after-free in uloop_handle_processes when a callback deletes a process</title>
<updated>2026-07-08T08:36:58Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-07-04T20:42:56Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/libubox/commit/?id=c08a4ab5312917e21104fd225136b2ae35e79427'/>
<id>urn:sha1:c08a4ab5312917e21104fd225136b2ae35e79427</id>
<content type='text'>
uloop_handle_processes() walked the process list with list_for_each_entry_safe(),
whose cached next entry became dangling if a process callback deleted and freed
another process watcher (duplicate pids are allowed, so a sibling with the same
pid could be freed mid-dispatch). Track the next entry in a module-level pointer
advanced by uloop_process_delete(), matching the signal_consume() fix, so the
loop tolerates arbitrary deletions.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>uloop: fix use-after-free in signal_consume when a callback deletes a watcher</title>
<updated>2026-07-08T08:34:44Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-07-04T20:41:40Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/libubox/commit/?id=a9ab90bd1d5d6cf96051700bce395579264d6c5f'/>
<id>urn:sha1:a9ab90bd1d5d6cf96051700bce395579264d6c5f</id>
<content type='text'>
signal_consume() dispatched signal callbacks with list_for_each_entry_safe(),
which caches the next entry. A callback that deleted and freed a different
signal watcher (its own successor in the list) left the iterator pointing at
freed memory, dereferenced on the next iteration. Track the next entry to be
visited in a module-level pointer and advance it from uloop_signal_delete()
when that entry is removed, so the loop stays valid across arbitrary deletions,
mirroring the cur_fds[] fixup used for fd dispatch.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
</feed>
