Skip to content

Commit 1dacc76

Browse files
jmbergdavem330
authored andcommitted
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>
1 parent 4f45b2c commit 1dacc76

File tree

8 files changed

+160
-7
lines changed

8 files changed

+160
-7
lines changed

arch/mips/kernel/scall64-n32.S

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ EXPORT(sysn32_call_table)
164164
PTR sys_connect
165165
PTR sys_accept
166166
PTR sys_sendto
167-
PTR sys_recvfrom
167+
PTR compat_sys_recvfrom
168168
PTR compat_sys_sendmsg /* 6045 */
169169
PTR compat_sys_recvmsg
170170
PTR sys_shutdown

arch/mips/kernel/scall64-o32.S

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,8 @@ sys_call_table:
378378
PTR sys_getsockname
379379
PTR sys_getsockopt
380380
PTR sys_listen
381-
PTR sys_recv /* 4175 */
382-
PTR sys_recvfrom
381+
PTR compat_sys_recv /* 4175 */
382+
PTR compat_sys_recvfrom
383383
PTR compat_sys_recvmsg
384384
PTR sys_send
385385
PTR compat_sys_sendmsg

arch/sparc/kernel/sys32.S

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ SIGN2(sys32_syslog, sys_syslog, %o0, %o2)
121121
SIGN1(sys32_umask, sys_umask, %o0)
122122
SIGN3(sys32_tgkill, sys_tgkill, %o0, %o1, %o2)
123123
SIGN1(sys32_sendto, sys_sendto, %o0)
124-
SIGN1(sys32_recvfrom, sys_recvfrom, %o0)
124+
SIGN1(sys32_recvfrom, compat_sys_recvfrom, %o0)
125125
SIGN3(sys32_socket, sys_socket, %o0, %o1, %o2)
126126
SIGN2(sys32_connect, sys_connect, %o0, %o2)
127127
SIGN2(sys32_bind, sys_bind, %o0, %o2)

include/linux/wireless.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,14 @@ struct __compat_iw_event {
11321132
};
11331133
#define IW_EV_COMPAT_LCP_LEN offsetof(struct __compat_iw_event, pointer)
11341134
#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))
11351143
#define IW_EV_COMPAT_POINT_LEN \
11361144
(IW_EV_COMPAT_LCP_LEN + sizeof(struct compat_iw_point) - \
11371145
IW_EV_COMPAT_POINT_OFF)

net/Kconfig

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,26 @@ menuconfig NET
2323

2424
if NET
2525

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+
2646
menu "Networking options"
2747

2848
source "net/packet/Kconfig"

net/compat.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,18 @@ asmlinkage long compat_sys_recvmsg(int fd, struct compat_msghdr __user *msg, uns
743743
return sys_recvmsg(fd, (struct msghdr __user *)msg, flags | MSG_CMSG_COMPAT);
744744
}
745745

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+
746758
asmlinkage long compat_sys_socketcall(int call, u32 __user *args)
747759
{
748760
int ret;
@@ -788,10 +800,11 @@ asmlinkage long compat_sys_socketcall(int call, u32 __user *args)
788800
ret = sys_sendto(a0, compat_ptr(a1), a[2], a[3], compat_ptr(a[4]), a[5]);
789801
break;
790802
case SYS_RECV:
791-
ret = sys_recv(a0, compat_ptr(a1), a[2], a[3]);
803+
ret = compat_sys_recv(a0, compat_ptr(a1), a[2], a[3]);
792804
break;
793805
case SYS_RECVFROM:
794-
ret = sys_recvfrom(a0, compat_ptr(a1), a[2], a[3], compat_ptr(a[4]), compat_ptr(a[5]));
806+
ret = compat_sys_recvfrom(a0, compat_ptr(a1), a[2], a[3],
807+
compat_ptr(a[4]), compat_ptr(a[5]));
795808
break;
796809
case SYS_SHUTDOWN:
797810
ret = sys_shutdown(a0,a1);

net/netlink/af_netlink.c

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1361,7 +1361,7 @@ static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,
13611361
struct netlink_sock *nlk = nlk_sk(sk);
13621362
int noblock = flags&MSG_DONTWAIT;
13631363
size_t copied;
1364-
struct sk_buff *skb;
1364+
struct sk_buff *skb, *frag __maybe_unused = NULL;
13651365
int err;
13661366

13671367
if (flags&MSG_OOB)
@@ -1373,6 +1373,35 @@ static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,
13731373
if (skb == NULL)
13741374
goto out;
13751375

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
1404+
13761405
msg->msg_namelen = 0;
13771406

13781407
copied = skb->len;
@@ -1403,6 +1432,11 @@ static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,
14031432
siocb->scm->creds = *NETLINK_CREDS(skb);
14041433
if (flags & MSG_TRUNC)
14051434
copied = skb->len;
1435+
1436+
#ifdef CONFIG_COMPAT_NETLINK_MESSAGES
1437+
skb_shinfo(skb)->frag_list = frag;
1438+
#endif
1439+
14061440
skb_free_datagram(sk, skb);
14071441

14081442
if (nlk->cb && atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2)

net/wireless/wext.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,21 @@ static const int event_type_size[] = {
417417
IW_EV_QUAL_LEN, /* IW_HEADER_TYPE_QUAL */
418418
};
419419

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
420435

421436
/************************ COMMON SUBROUTINES ************************/
422437
/*
@@ -1348,6 +1363,22 @@ void wireless_send_event(struct net_device * dev,
13481363
struct sk_buff *skb;
13491364
struct nlmsghdr *nlh;
13501365
struct nlattr *nla;
1366+
#ifdef CONFIG_COMPAT
1367+
struct __compat_iw_event *compat_event;
1368+
struct compat_iw_point compat_wrqu;
1369+
struct sk_buff *compskb;
1370+
#endif
1371+
1372+
/*
1373+
* Nothing in the kernel sends scan events with data, be safe.
1374+
* This is necessary because we cannot fix up scan event data
1375+
* for compat, due to being contained in 'extra', but normally
1376+
* applications are required to retrieve the scan data anyway
1377+
* and no data is included in the event, this codifies that
1378+
* practice.
1379+
*/
1380+
if (WARN_ON(cmd == SIOCGIWSCAN && extra))
1381+
extra = NULL;
13511382

13521383
/* Get the description of the Event */
13531384
if (cmd <= SIOCIWLAST) {
@@ -1446,7 +1477,54 @@ void wireless_send_event(struct net_device * dev,
14461477
memcpy(((char *) event) + hdr_len, extra, extra_len);
14471478

14481479
nlmsg_end(skb, nlh);
1480+
#ifdef CONFIG_COMPAT
1481+
hdr_len = compat_event_type_size[descr->header_type];
1482+
event_len = hdr_len + extra_len;
14491483

1484+
compskb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
1485+
if (!compskb) {
1486+
kfree_skb(skb);
1487+
return;
1488+
}
1489+
1490+
/* Send via the RtNetlink event channel */
1491+
nlh = rtnetlink_ifinfo_prep(dev, compskb);
1492+
if (WARN_ON(!nlh)) {
1493+
kfree_skb(skb);
1494+
kfree_skb(compskb);
1495+
return;
1496+
}
1497+
1498+
/* Add the wireless events in the netlink packet */
1499+
nla = nla_reserve(compskb, IFLA_WIRELESS, event_len);
1500+
if (!nla) {
1501+
kfree_skb(skb);
1502+
kfree_skb(compskb);
1503+
return;
1504+
}
1505+
compat_event = nla_data(nla);
1506+
1507+
compat_event->len = event_len;
1508+
compat_event->cmd = cmd;
1509+
if (descr->header_type == IW_HEADER_TYPE_POINT) {
1510+
compat_wrqu.length = wrqu->data.length;
1511+
compat_wrqu.flags = wrqu->data.flags;
1512+
memcpy(&compat_event->pointer,
1513+
((char *) &compat_wrqu) + IW_EV_COMPAT_POINT_OFF,
1514+
hdr_len - IW_EV_COMPAT_LCP_LEN);
1515+
if (extra_len)
1516+
memcpy(((char *) compat_event) + hdr_len,
1517+
extra, extra_len);
1518+
} else {
1519+
/* extra_len must be zero, so no if (extra) needed */
1520+
memcpy(&compat_event->pointer, wrqu,
1521+
hdr_len - IW_EV_COMPAT_LCP_LEN);
1522+
}
1523+
1524+
nlmsg_end(compskb, nlh);
1525+
1526+
skb_shinfo(skb)->frag_list = compskb;
1527+
#endif
14501528
skb_queue_tail(&dev_net(dev)->wext_nlevents, skb);
14511529
schedule_work(&wireless_nlevent_work);
14521530
}

0 commit comments

Comments
 (0)