map_newlink() may abort when interface list changed between netlink
request for getting interfaces and getting addresses. This commit is
ported from the same change from glibc commit.
Signed-off-by: Vincent Hou <vincent.houyi(a)gmail.com>
---
Hi all,
App code making a getaddrinfo() call may get a occasional abort from
map_newlink(). The reason of the abort is getifaddrs() uses an out-dated
interface list.
In getaddrinfo() call, it will call a function getifaddrs() who retrieves
all interfaces and addresses from kernel via netlink, returning an array
of interfaces and addresses. It will then call a function map_newlink()
to build a mapping between the interfaces’ index into the returning array
to the interface id in kernel. Then getifaddrs() will bind the
relationship between interface and the address using the mapping from
previous step.
The interfaces and addresses are retrieved from two separate netlink
requests. It retrieves interface lists then followed by address list.
Between two requests, kernel may make change to interfaces and addresses.
If a new interface and address is added between the requests, the
newly-added interface doesn’t present in the interface list, but the
new-added address does. So the address will point to an non-existent
interface index.
This issue is fixed by glibc and this change ports the commit from glibc
(with some minor changes).
Thanks,
Vincent Hou
libc/inet/ifaddrs.c | 53 +++++++++++++++++++++++++++++++++++----------
1 file changed, 42 insertions(+), 11 deletions(-)
diff --git a/libc/inet/ifaddrs.c b/libc/inet/ifaddrs.c
index 0c9310651..72771d35a 100644
--- a/libc/inet/ifaddrs.c
+++ b/libc/inet/ifaddrs.c
@@ -339,17 +339,19 @@ map_newlink (int idx, struct ifaddrs_storage *ifas, int *map, int max)
else if (map[i] == idx)
return i;
}
- /* This should never be reached. If this will be reached, we have
- a very big problem. */
- abort ();
+
+ /* This means interfaces changed inbetween the reading of the
+ RTM_GETLINK and RTM_GETADDR information. We have to repeat
+ everything. */
+ return -1;
}
/* Create a linked list of `struct ifaddrs' structures, one for each
network interface on the host machine. If successful, store the
list in *IFAP and return 0. On errors, return -1 and set `errno'. */
-int
-getifaddrs (struct ifaddrs **ifap)
+static int
+getifaddrs_internal (struct ifaddrs **ifap)
{
struct netlink_handle nh = { 0, 0, 0, NULL, NULL };
struct netlink_res *nlp;
@@ -496,6 +498,13 @@ getifaddrs (struct ifaddrs **ifap)
kernel. */
ifa_index = map_newlink (ifim->ifi_index - 1, ifas,
map_newlink_data, newlink);
+ if (__builtin_expect (ifa_index == -1, 0))
+ {
+ try_again:
+ result = -EAGAIN;
+ free (ifas);
+ goto exit_free;
+ }
ifas[ifa_index].ifa.ifa_flags = ifim->ifi_flags;
while (RTA_OK (rta, rtasize))
@@ -580,9 +589,11 @@ getifaddrs (struct ifaddrs **ifap)
that we have holes in the interface part of the list,
but we always have already the interface for this address. */
ifa_index = newlink + newaddr_idx;
- ifas[ifa_index].ifa.ifa_flags
- = ifas[map_newlink (ifam->ifa_index - 1, ifas,
- map_newlink_data, newlink)].ifa.ifa_flags;
+ int idx = map_newlink (ifam->ifa_index - 1, ifas,
+ map_newlink_data, newlink);
+ if (__builtin_expect (idx == -1, 0))
+ goto try_again;
+ ifas[ifa_index].ifa.ifa_flags = ifas[idx].ifa.ifa_flags;
if (ifa_index > 0)
ifas[ifa_index - 1].ifa.ifa_next = &ifas[ifa_index].ifa;
++newaddr_idx;
@@ -768,9 +779,13 @@ getifaddrs (struct ifaddrs **ifap)
/* If we didn't get the interface name with the
address, use the name from the interface entry. */
if (ifas[ifa_index].ifa.ifa_name == NULL)
- ifas[ifa_index].ifa.ifa_name
- = ifas[map_newlink (ifam->ifa_index - 1, ifas,
- map_newlink_data, newlink)].ifa.ifa_name;
+ {
+ int idx = map_newlink (ifam->ifa_index - 1, ifas,
+ map_newlink_data, newlink);
+ if (__builtin_expect (idx == -1, 0))
+ goto try_again;
+ ifas[ifa_index].ifa.ifa_name = ifas[idx].ifa.ifa_name;
+ }
/* Calculate the netmask. */
if (ifas[ifa_index].ifa.ifa_addr
@@ -850,6 +865,22 @@ getifaddrs (struct ifaddrs **ifap)
return result;
}
+
+
+/* Create a linked list of `struct ifaddrs' structures, one for each
+ network interface on the host machine. If successful, store the
+ list in *IFAP and return 0. On errors, return -1 and set `errno'. */
+int
+getifaddrs (struct ifaddrs **ifap)
+{
+ int res;
+
+ do
+ res = getifaddrs_internal (ifap);
+ while (res == -EAGAIN);
+
+ return res;
+}
libc_hidden_def(getifaddrs)
void
--
2.18.0
From: Yann Sionneau <ysionneau(a)kalray.eu>
Avoid calling select with empty sets which hangs the process
This makes uClibc-ng act like glibc and musl
Without this fix the test_poll of python3 testsuite hangs forever
Scenario of the issue:
If you call poll with only invalid file descriptors, like in python3
testsuite
(https://github.com/python/cpython/blob/master/Lib/test/test_poll.py#L83)
You will go through uClibc poll emulation code, which is based on
select syscall.
Your first call to select will fail, it will return -1 and errno will be
set to EBADF: https://github.com/wbx-github/uclibc-ng/blob/master/libc/sysdeps/linux/comm…
Then you will go through the for loop which tests individually each file descriptor by calling
select on each one: https://github.com/wbx-github/uclibc-ng/blob/master/libc/sysdeps/linux/comm…
each call will also return -1 with errno being equal to EBADF.
Therefore all pollfd will have the POLLNVAL flag in their respective revents field.
And, the most important, rset/wset/xset will stay empty.
Then the for loop ends, the "continue" makes the while loop run again.
The following select() is run again: https://github.com/wbx-github/uclibc-ng/blob/master/libc/sysdeps/linux/comm…
But this time the sets are empty.
If the poll was called with timeout set to -1, this select will hang forever because there is no timeout
and the sets are empty so no event will ever wake it up.
test program:
int main(void)
{
struct pollfd pfd;
int ret;
int pipe_fds[2];
pipe(pipe_fds);
close(pipe_fds[0]);
close(pipe_fds[1]);
pfd.fd = pipe_fds[0];
pfd.events = POLLIN | POLLOUT | POLLPRI;
pfd.revents = 0;
ret = poll(&pfd, 1, -1);
printf("ret: %d\n", ret);
if (ret < 0)
printf("error: %s", strerror(errno));
else {
puts("revents: ");
if (pfd.revents & POLLERR)
printf(" POLLERR");
if (pfd.revents & POLLHUP)
printf(" POLLHUP");
if (pfd.revents & POLLNVAL)
printf(" POLLNVAL");
puts("");
}
return 0;
}
This hangs on uClibc-ng aarch64 and Kalray's arch (kv3) but does the following on musl and glibc:
"
ret: 1
revents:
POLLNVAL
"
strace output of this program with uClibc *without* the patch applied:
pselect6(4, [3], [3], [3], NULL, NULL) = -1 EBADF (Bad file descriptor)
pselect6(4, [3], [3], [3], {tv_sec=0, tv_nsec=0}, NULL) = -1 EBADF (Bad file descriptor)
pselect6(0, 0x7ffffffb80, 0x7ffffffb68, 0x7ffffffb50, NULL, NULL
(never finishes)
strace output of this program with uClibc *with* the patch applied:
pselect6(4, [3], [3], [3], NULL, NULL) = -1 EBADF (Bad file descriptor)
pselect6(4, [3], [3], [3], {tv_sec=0, tv_nsec=0}, NULL) = -1 EBADF (Bad file descriptor)
write(1, "ret: 1\n", 7ret: 1
) = 7
write(1, "revents: \n", 10revents:
) = 10
write(1, " POLLNVAL\n", 10 POLLNVAL
) = 10
exit_group(0) = ?
+++ exited with 0 +++
---
libc/sysdeps/linux/common/poll.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/libc/sysdeps/linux/common/poll.c b/libc/sysdeps/linux/common/poll.c
index d1f1f17..3d46d5b 100644
--- a/libc/sysdeps/linux/common/poll.c
+++ b/libc/sysdeps/linux/common/poll.c
@@ -53,6 +53,7 @@ int __NC(poll)(struct pollfd *fds, nfds_t nfds, int timeout)
fd_set *rset, *wset, *xset;
struct pollfd *f;
int ready;
+ int error_num;
int maxfd = 0;
int bytes;
@@ -142,6 +143,7 @@ int __NC(poll)(struct pollfd *fds, nfds_t nfds, int timeout)
/* Reset the return value. */
ready = 0;
+ error_num = 0;
for (f = fds; f < &fds[nfds]; ++f)
if (f->fd != -1 && (f->events & (POLLIN|POLLOUT|POLLPRI))
@@ -178,8 +180,13 @@ int __NC(poll)(struct pollfd *fds, nfds_t nfds, int timeout)
++ready;
}
else if (errno == EBADF)
+ {
f->revents |= POLLNVAL;
+ error_num++;
+ }
}
+ if (ready == 0)
+ return error_num;
/* Try again. */
continue;
}
--
1.8.3.1