Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net/compat/wext: send different messages to compat tasks

Wireless extensions have the unfortunate problem that events
are multicast netlink messages, and are not independent of
pointer size. Thus, currently 32-bit tasks on 64-bit platforms
cannot properly receive events and fail with all kinds of
strange problems, for instance wpa_supplicant never notices
disassociations, due to the way the 64-bit event looks (to a
32-bit process), the fact that the address is all zeroes is
lost, it thinks instead it is 00:00:00:00:01:00.

The same problem existed with the ioctls, until David Miller
fixed those some time ago in an heroic effort.

A different problem caused by this is that we cannot send the
ASSOCREQIE/ASSOCRESPIE events because sending them causes a
32-bit wpa_supplicant on a 64-bit system to overwrite its
internal information, which is worse than it not getting the
information at all -- so we currently resort to sending a
custom string event that it then parses. This, however, has a
severe size limitation we are frequently hitting with modern
access points; this limitation would can be lifted after this
patch by sending the correct binary, not custom, event.

A similar problem apparently happens for some other netlink
users on x86_64 with 32-bit tasks due to the alignment for
64-bit quantities.

In order to fix these problems, I have implemented a way to
send compat messages to tasks. When sending an event, we send
the non-compat event data together with a compat event data in
skb_shinfo(main_skb)->frag_list. Then, when the event is read
from the socket, the netlink code makes sure to pass out only
the skb that is compatible with the task. This approach was
suggested by David Miller, my original approach required
always sending two skbs but that had various small problems.

To determine whether compat is needed or not, I have used the
MSG_CMSG_COMPAT flag, and adjusted the call path for recv and
recvfrom to include it, even if those calls do not have a cmsg
parameter.

I have not solved one small part of the problem, and I don't
think it is necessary to: if a 32-bit application uses read()
rather than any form of recvmsg() it will still get the wrong
(64-bit) event. However, neither do applications actually do
this, nor would it be a regression.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Johannes Berg and committed by
David S. Miller
1dacc76d 4f45b2cd

+160 -7
+1 -1
arch/mips/kernel/scall64-n32.S
··· 164 164 PTR sys_connect 165 165 PTR sys_accept 166 166 PTR sys_sendto 167 - PTR sys_recvfrom 167 + PTR compat_sys_recvfrom 168 168 PTR compat_sys_sendmsg /* 6045 */ 169 169 PTR compat_sys_recvmsg 170 170 PTR sys_shutdown
+2 -2
arch/mips/kernel/scall64-o32.S
··· 378 378 PTR sys_getsockname 379 379 PTR sys_getsockopt 380 380 PTR sys_listen 381 - PTR sys_recv /* 4175 */ 382 - PTR sys_recvfrom 381 + PTR compat_sys_recv /* 4175 */ 382 + PTR compat_sys_recvfrom 383 383 PTR compat_sys_recvmsg 384 384 PTR sys_send 385 385 PTR compat_sys_sendmsg
+1 -1
arch/sparc/kernel/sys32.S
··· 121 121 SIGN1(sys32_umask, sys_umask, %o0) 122 122 SIGN3(sys32_tgkill, sys_tgkill, %o0, %o1, %o2) 123 123 SIGN1(sys32_sendto, sys_sendto, %o0) 124 - SIGN1(sys32_recvfrom, sys_recvfrom, %o0) 124 + SIGN1(sys32_recvfrom, compat_sys_recvfrom, %o0) 125 125 SIGN3(sys32_socket, sys_socket, %o0, %o1, %o2) 126 126 SIGN2(sys32_connect, sys_connect, %o0, %o2) 127 127 SIGN2(sys32_bind, sys_bind, %o0, %o2)
+8
include/linux/wireless.h
··· 1132 1132 }; 1133 1133 #define IW_EV_COMPAT_LCP_LEN offsetof(struct __compat_iw_event, pointer) 1134 1134 #define IW_EV_COMPAT_POINT_OFF offsetof(struct compat_iw_point, length) 1135 + 1136 + /* Size of the various events for compat */ 1137 + #define IW_EV_COMPAT_CHAR_LEN (IW_EV_COMPAT_LCP_LEN + IFNAMSIZ) 1138 + #define IW_EV_COMPAT_UINT_LEN (IW_EV_COMPAT_LCP_LEN + sizeof(__u32)) 1139 + #define IW_EV_COMPAT_FREQ_LEN (IW_EV_COMPAT_LCP_LEN + sizeof(struct iw_freq)) 1140 + #define IW_EV_COMPAT_PARAM_LEN (IW_EV_COMPAT_LCP_LEN + sizeof(struct iw_param)) 1141 + #define IW_EV_COMPAT_ADDR_LEN (IW_EV_COMPAT_LCP_LEN + sizeof(struct sockaddr)) 1142 + #define IW_EV_COMPAT_QUAL_LEN (IW_EV_COMPAT_LCP_LEN + sizeof(struct iw_quality)) 1135 1143 #define IW_EV_COMPAT_POINT_LEN \ 1136 1144 (IW_EV_COMPAT_LCP_LEN + sizeof(struct compat_iw_point) - \ 1137 1145 IW_EV_COMPAT_POINT_OFF)
+20
net/Kconfig
··· 23 23 24 24 if NET 25 25 26 + config WANT_COMPAT_NETLINK_MESSAGES 27 + bool 28 + help 29 + This option can be selected by other options that need compat 30 + netlink messages. 31 + 32 + config COMPAT_NETLINK_MESSAGES 33 + def_bool y 34 + depends on COMPAT 35 + depends on WIRELESS_EXT || WANT_COMPAT_NETLINK_MESSAGES 36 + help 37 + This option makes it possible to send different netlink messages 38 + to tasks depending on whether the task is a compat task or not. To 39 + achieve this, you need to set skb_shinfo(skb)->frag_list to the 40 + compat skb before sending the skb, the netlink code will sort out 41 + which message to actually pass to the task. 42 + 43 + Newly written code should NEVER need this option but do 44 + compat-independent messages instead! 45 + 26 46 menu "Networking options" 27 47 28 48 source "net/packet/Kconfig"
+15 -2
net/compat.c
··· 743 743 return sys_recvmsg(fd, (struct msghdr __user *)msg, flags | MSG_CMSG_COMPAT); 744 744 } 745 745 746 + asmlinkage long compat_sys_recv(int fd, void __user *buf, size_t len, unsigned flags) 747 + { 748 + return sys_recv(fd, buf, len, flags | MSG_CMSG_COMPAT); 749 + } 750 + 751 + asmlinkage long compat_sys_recvfrom(int fd, void __user *buf, size_t len, 752 + unsigned flags, struct sockaddr __user *addr, 753 + int __user *addrlen) 754 + { 755 + return sys_recvfrom(fd, buf, len, flags | MSG_CMSG_COMPAT, addr, addrlen); 756 + } 757 + 746 758 asmlinkage long compat_sys_socketcall(int call, u32 __user *args) 747 759 { 748 760 int ret; ··· 800 788 ret = sys_sendto(a0, compat_ptr(a1), a[2], a[3], compat_ptr(a[4]), a[5]); 801 789 break; 802 790 case SYS_RECV: 803 - ret = sys_recv(a0, compat_ptr(a1), a[2], a[3]); 791 + ret = compat_sys_recv(a0, compat_ptr(a1), a[2], a[3]); 804 792 break; 805 793 case SYS_RECVFROM: 806 - ret = sys_recvfrom(a0, compat_ptr(a1), a[2], a[3], compat_ptr(a[4]), compat_ptr(a[5])); 794 + ret = compat_sys_recvfrom(a0, compat_ptr(a1), a[2], a[3], 795 + compat_ptr(a[4]), compat_ptr(a[5])); 807 796 break; 808 797 case SYS_SHUTDOWN: 809 798 ret = sys_shutdown(a0,a1);
+35 -1
net/netlink/af_netlink.c
··· 1361 1361 struct netlink_sock *nlk = nlk_sk(sk); 1362 1362 int noblock = flags&MSG_DONTWAIT; 1363 1363 size_t copied; 1364 - struct sk_buff *skb; 1364 + struct sk_buff *skb, *frag __maybe_unused = NULL; 1365 1365 int err; 1366 1366 1367 1367 if (flags&MSG_OOB) ··· 1372 1372 skb = skb_recv_datagram(sk, flags, noblock, &err); 1373 1373 if (skb == NULL) 1374 1374 goto out; 1375 + 1376 + #ifdef CONFIG_COMPAT_NETLINK_MESSAGES 1377 + if (unlikely(skb_shinfo(skb)->frag_list)) { 1378 + bool need_compat = !!(flags & MSG_CMSG_COMPAT); 1379 + 1380 + /* 1381 + * If this skb has a frag_list, then here that means that 1382 + * we will have to use the frag_list skb for compat tasks 1383 + * and the regular skb for non-compat tasks. 1384 + * 1385 + * The skb might (and likely will) be cloned, so we can't 1386 + * just reset frag_list and go on with things -- we need to 1387 + * keep that. For the compat case that's easy -- simply get 1388 + * a reference to the compat skb and free the regular one 1389 + * including the frag. For the non-compat case, we need to 1390 + * avoid sending the frag to the user -- so assign NULL but 1391 + * restore it below before freeing the skb. 1392 + */ 1393 + if (need_compat) { 1394 + struct sk_buff *compskb = skb_shinfo(skb)->frag_list; 1395 + skb_get(compskb); 1396 + kfree_skb(skb); 1397 + skb = compskb; 1398 + } else { 1399 + frag = skb_shinfo(skb)->frag_list; 1400 + skb_shinfo(skb)->frag_list = NULL; 1401 + } 1402 + } 1403 + #endif 1375 1404 1376 1405 msg->msg_namelen = 0; 1377 1406 ··· 1432 1403 siocb->scm->creds = *NETLINK_CREDS(skb); 1433 1404 if (flags & MSG_TRUNC) 1434 1405 copied = skb->len; 1406 + 1407 + #ifdef CONFIG_COMPAT_NETLINK_MESSAGES 1408 + skb_shinfo(skb)->frag_list = frag; 1409 + #endif 1410 + 1435 1411 skb_free_datagram(sk, skb); 1436 1412 1437 1413 if (nlk->cb && atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2)
+78
net/wireless/wext.c
··· 417 417 IW_EV_QUAL_LEN, /* IW_HEADER_TYPE_QUAL */ 418 418 }; 419 419 420 + #ifdef CONFIG_COMPAT 421 + static const int compat_event_type_size[] = { 422 + IW_EV_COMPAT_LCP_LEN, /* IW_HEADER_TYPE_NULL */ 423 + 0, 424 + IW_EV_COMPAT_CHAR_LEN, /* IW_HEADER_TYPE_CHAR */ 425 + 0, 426 + IW_EV_COMPAT_UINT_LEN, /* IW_HEADER_TYPE_UINT */ 427 + IW_EV_COMPAT_FREQ_LEN, /* IW_HEADER_TYPE_FREQ */ 428 + IW_EV_COMPAT_ADDR_LEN, /* IW_HEADER_TYPE_ADDR */ 429 + 0, 430 + IW_EV_COMPAT_POINT_LEN, /* Without variable payload */ 431 + IW_EV_COMPAT_PARAM_LEN, /* IW_HEADER_TYPE_PARAM */ 432 + IW_EV_COMPAT_QUAL_LEN, /* IW_HEADER_TYPE_QUAL */ 433 + }; 434 + #endif 420 435 421 436 /************************ COMMON SUBROUTINES ************************/ 422 437 /* ··· 1363 1348 struct sk_buff *skb; 1364 1349 struct nlmsghdr *nlh; 1365 1350 struct nlattr *nla; 1351 + #ifdef CONFIG_COMPAT 1352 + struct __compat_iw_event *compat_event; 1353 + struct compat_iw_point compat_wrqu; 1354 + struct sk_buff *compskb; 1355 + #endif 1356 + 1357 + /* 1358 + * Nothing in the kernel sends scan events with data, be safe. 1359 + * This is necessary because we cannot fix up scan event data 1360 + * for compat, due to being contained in 'extra', but normally 1361 + * applications are required to retrieve the scan data anyway 1362 + * and no data is included in the event, this codifies that 1363 + * practice. 1364 + */ 1365 + if (WARN_ON(cmd == SIOCGIWSCAN && extra)) 1366 + extra = NULL; 1366 1367 1367 1368 /* Get the description of the Event */ 1368 1369 if (cmd <= SIOCIWLAST) { ··· 1477 1446 memcpy(((char *) event) + hdr_len, extra, extra_len); 1478 1447 1479 1448 nlmsg_end(skb, nlh); 1449 + #ifdef CONFIG_COMPAT 1450 + hdr_len = compat_event_type_size[descr->header_type]; 1451 + event_len = hdr_len + extra_len; 1480 1452 1453 + compskb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); 1454 + if (!compskb) { 1455 + kfree_skb(skb); 1456 + return; 1457 + } 1458 + 1459 + /* Send via the RtNetlink event channel */ 1460 + nlh = rtnetlink_ifinfo_prep(dev, compskb); 1461 + if (WARN_ON(!nlh)) { 1462 + kfree_skb(skb); 1463 + kfree_skb(compskb); 1464 + return; 1465 + } 1466 + 1467 + /* Add the wireless events in the netlink packet */ 1468 + nla = nla_reserve(compskb, IFLA_WIRELESS, event_len); 1469 + if (!nla) { 1470 + kfree_skb(skb); 1471 + kfree_skb(compskb); 1472 + return; 1473 + } 1474 + compat_event = nla_data(nla); 1475 + 1476 + compat_event->len = event_len; 1477 + compat_event->cmd = cmd; 1478 + if (descr->header_type == IW_HEADER_TYPE_POINT) { 1479 + compat_wrqu.length = wrqu->data.length; 1480 + compat_wrqu.flags = wrqu->data.flags; 1481 + memcpy(&compat_event->pointer, 1482 + ((char *) &compat_wrqu) + IW_EV_COMPAT_POINT_OFF, 1483 + hdr_len - IW_EV_COMPAT_LCP_LEN); 1484 + if (extra_len) 1485 + memcpy(((char *) compat_event) + hdr_len, 1486 + extra, extra_len); 1487 + } else { 1488 + /* extra_len must be zero, so no if (extra) needed */ 1489 + memcpy(&compat_event->pointer, wrqu, 1490 + hdr_len - IW_EV_COMPAT_LCP_LEN); 1491 + } 1492 + 1493 + nlmsg_end(compskb, nlh); 1494 + 1495 + skb_shinfo(skb)->frag_list = compskb; 1496 + #endif 1481 1497 skb_queue_tail(&dev_net(dev)->wext_nlevents, skb); 1482 1498 schedule_work(&wireless_nlevent_work); 1483 1499 }