From ba41dc8a7009f9f28548c8fc980f15b9c671c754 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" Date: Thu, 16 Nov 2017 02:27:40 +0000 Subject: [PATCH] net: fix SOL_NETLINK NETLINK_LIST_MEMBERSHIPS decoding NETLINK_LIST_MEMBERSHIPS, unlike all other SOL_NETLINK options, requests not just a single integer but an array of integers. The kernel also supports a zero optlen NETLINK_LIST_MEMBERSHIPS request. * net.c (print_uint32): New function. (print_getsockopt): Add ulen argument, rename len argument to rlen, Handle NETLINK_LIST_MEMBERSHIPS using print_array and print_uint32. (SYS_FUNC(getsockopt)): Pass ulen to print_getsockopt. * tests/sockopt-sol_netlink.c (main): Check NETLINK_LIST_MEMBERSHIPS decoding. --- net.c | 52 +++++++++++++++++++++++++++---------- tests/sockopt-sol_netlink.c | 39 ++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/net.c b/net.c index 3e355e38..498a6384 100644 --- a/net.c +++ b/net.c @@ -556,27 +556,35 @@ print_icmp_filter(struct tcb *const tcp, const kernel_ulong_t addr, int len) tprints(")"); } +static bool +print_uint32(struct tcb *tcp, void *elem_buf, size_t elem_size, void *data) +{ + tprintf("%u", *(uint32_t *) elem_buf); + + return true; +} + static void print_getsockopt(struct tcb *const tcp, const unsigned int level, const unsigned int name, const kernel_ulong_t addr, - const int len) + const int ulen, const int rlen) { if (addr && verbose(tcp)) switch (level) { case SOL_SOCKET: switch (name) { case SO_LINGER: - print_get_linger(tcp, addr, len); + print_get_linger(tcp, addr, rlen); return; #ifdef SO_PEERCRED case SO_PEERCRED: - print_ucred(tcp, addr, len); + print_ucred(tcp, addr, rlen); return; #endif #ifdef SO_ATTACH_FILTER case SO_ATTACH_FILTER: - if (len && (unsigned short) len == (unsigned int) len) - print_sock_fprog(tcp, addr, len); + if (rlen && (unsigned short) rlen == (unsigned int) rlen) + print_sock_fprog(tcp, addr, rlen); else printaddr(addr); return; @@ -588,7 +596,7 @@ print_getsockopt(struct tcb *const tcp, const unsigned int level, switch (name) { #ifdef PACKET_STATISTICS case PACKET_STATISTICS: - print_tpacket_stats(tcp, addr, len); + print_tpacket_stats(tcp, addr, rlen); return; #endif } @@ -597,26 +605,44 @@ print_getsockopt(struct tcb *const tcp, const unsigned int level, case SOL_RAW: switch (name) { case ICMP_FILTER: - print_icmp_filter(tcp, addr, len); + print_icmp_filter(tcp, addr, rlen); return; } break; case SOL_NETLINK: - if (len < (int) sizeof(int)) - printaddr(addr); /* unlikely */ - else + if (ulen < 0 || rlen < 0) { + /* + * As the kernel neither accepts nor returns a negative + * length, in case of successful getsockopt syscall + * invocation these negative values must have come + * from userspace. + */ + printaddr(addr); + return; + } + switch (name) { + case NETLINK_LIST_MEMBERSHIPS: { + uint32_t buf; + print_array(tcp, addr, MIN(ulen, rlen) / sizeof(buf), + &buf, sizeof(buf), + umoven_or_printaddr, print_uint32, 0); + break; + } + default: printnum_int(tcp, addr, "%d"); + break; + } return; } /* default arg printing */ if (verbose(tcp)) { - if (len == sizeof(int)) { + if (rlen == sizeof(int)) { printnum_int(tcp, addr, "%d"); } else { - printstrn(tcp, addr, len); + printstrn(tcp, addr, rlen); } } else { printaddr(addr); @@ -649,7 +675,7 @@ SYS_FUNC(getsockopt) tprintf(", [%d]", ulen); } else { print_getsockopt(tcp, tcp->u_arg[1], tcp->u_arg[2], - tcp->u_arg[3], rlen); + tcp->u_arg[3], ulen, rlen); if (ulen != rlen) tprintf(", [%d->%d]", ulen, rlen); else diff --git a/tests/sockopt-sol_netlink.c b/tests/sockopt-sol_netlink.c index b17f9333..066920c6 100644 --- a/tests/sockopt-sol_netlink.c +++ b/tests/sockopt-sol_netlink.c @@ -85,6 +85,9 @@ main(void) #ifdef NETLINK_LISTEN_ALL_NSID { ARG_STR(NETLINK_LISTEN_ALL_NSID) }, #endif +#ifdef NETLINK_LIST_MEMBERSHIPS + { ARG_STR(NETLINK_LIST_MEMBERSHIPS) }, +#endif #ifdef NETLINK_CAP_ACK { ARG_STR(NETLINK_CAP_ACK) }, #endif @@ -136,14 +139,34 @@ main(void) printf("->%d", *len); printf("]) = %s\n", errstr); - /* optlen shorter than necessary - print address */ - *len = sizeof(*val) - 1; - get_sockopt(fd, names[i].val, val, len); - printf("getsockopt(%d, SOL_NETLINK, %s, %p, [%d", - fd, names[i].str, val, (int) sizeof(*val) - 1); - if ((int) sizeof(*val) - 1 != *len) - printf("->%d", *len); - printf("]) = %s\n", errstr); +#ifdef NETLINK_LIST_MEMBERSHIPS + if (names[i].val != NETLINK_LIST_MEMBERSHIPS) { +#endif + /* optlen shorter than necessary - print address */ + *len = sizeof(*val) - 1; + get_sockopt(fd, names[i].val, val, len); + printf("getsockopt(%d, SOL_NETLINK, %s, %p, [%d", + fd, names[i].str, val, (int) sizeof(*val) - 1); + if ((int) sizeof(*val) - 1 != *len) + printf("->%d", *len); + printf("]) = %s\n", errstr); +#ifdef NETLINK_LIST_MEMBERSHIPS + } else { + /* optlen shorter than required for the first element */ + *len = sizeof(*val) - 1; + get_sockopt(fd, names[i].val, efault, len); + printf("getsockopt(%d, SOL_NETLINK, %s, ", + fd, names[i].str); + if (rc) + printf("%p", efault); + else + printf("[]"); + printf(", [%d", (int) sizeof(*val) - 1); + if ((int) sizeof(*val) - 1 != *len) + printf("->%d", *len); + printf("]) = %s\n", errstr); + } +#endif /* optval EFAULT - print address */ *len = sizeof(*val); -- 2.40.0