Skip to content

Commit a9ba23d

Browse files
pcmooredavem330
authored andcommitted
ipv6: make ipv6_renew_options() interrupt/kernel safe
At present the ipv6_renew_options_kern() function ends up calling into access_ok() which is problematic if done from inside an interrupt as access_ok() calls WARN_ON_IN_IRQ() on some (all?) architectures (x86-64 is affected). Example warning/backtrace is shown below: WARNING: CPU: 1 PID: 3144 at lib/usercopy.c:11 _copy_from_user+0x85/0x90 ... Call Trace: <IRQ> ipv6_renew_option+0xb2/0xf0 ipv6_renew_options+0x26a/0x340 ipv6_renew_options_kern+0x2c/0x40 calipso_req_setattr+0x72/0xe0 netlbl_req_setattr+0x126/0x1b0 selinux_netlbl_inet_conn_request+0x80/0x100 selinux_inet_conn_request+0x6d/0xb0 security_inet_conn_request+0x32/0x50 tcp_conn_request+0x35f/0xe00 ? __lock_acquire+0x250/0x16c0 ? selinux_socket_sock_rcv_skb+0x1ae/0x210 ? tcp_rcv_state_process+0x289/0x106b tcp_rcv_state_process+0x289/0x106b ? tcp_v6_do_rcv+0x1a7/0x3c0 tcp_v6_do_rcv+0x1a7/0x3c0 tcp_v6_rcv+0xc82/0xcf0 ip6_input_finish+0x10d/0x690 ip6_input+0x45/0x1e0 ? ip6_rcv_finish+0x1d0/0x1d0 ipv6_rcv+0x32b/0x880 ? ip6_make_skb+0x1e0/0x1e0 __netif_receive_skb_core+0x6f2/0xdf0 ? process_backlog+0x85/0x250 ? process_backlog+0x85/0x250 ? process_backlog+0xec/0x250 process_backlog+0xec/0x250 net_rx_action+0x153/0x480 __do_softirq+0xd9/0x4f7 do_softirq_own_stack+0x2a/0x40 </IRQ> ... While not present in the backtrace, ipv6_renew_option() ends up calling access_ok() via the following chain: access_ok() _copy_from_user() copy_from_user() ipv6_renew_option() The fix presented in this patch is to perform the userspace copy earlier in the call chain such that it is only called when the option data is actually coming from userspace; that place is do_ipv6_setsockopt(). Not only does this solve the problem seen in the backtrace above, it also allows us to simplify the code quite a bit by removing ipv6_renew_options_kern() completely. We also take this opportunity to cleanup ipv6_renew_options()/ipv6_renew_option() a small amount as well. This patch is heavily based on a rough patch by Al Viro. I've taken his original patch, converted a kmemdup() call in do_ipv6_setsockopt() to a memdup_user() call, made better use of the e_inval jump target in the same function, and cleaned up the use ipv6_renew_option() by ipv6_renew_options(). CC: Al Viro <[email protected]> Signed-off-by: Paul Moore <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 0df8adb commit a9ba23d

File tree

4 files changed

+53
-103
lines changed

4 files changed

+53
-103
lines changed

include/net/ipv6.h

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -355,14 +355,7 @@ struct ipv6_txoptions *ipv6_dup_options(struct sock *sk,
355355
struct ipv6_txoptions *ipv6_renew_options(struct sock *sk,
356356
struct ipv6_txoptions *opt,
357357
int newtype,
358-
struct ipv6_opt_hdr __user *newopt,
359-
int newoptlen);
360-
struct ipv6_txoptions *
361-
ipv6_renew_options_kern(struct sock *sk,
362-
struct ipv6_txoptions *opt,
363-
int newtype,
364-
struct ipv6_opt_hdr *newopt,
365-
int newoptlen);
358+
struct ipv6_opt_hdr *newopt);
366359
struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space,
367360
struct ipv6_txoptions *opt);
368361

net/ipv6/calipso.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -799,8 +799,7 @@ static int calipso_opt_update(struct sock *sk, struct ipv6_opt_hdr *hop)
799799
{
800800
struct ipv6_txoptions *old = txopt_get(inet6_sk(sk)), *txopts;
801801

802-
txopts = ipv6_renew_options_kern(sk, old, IPV6_HOPOPTS,
803-
hop, hop ? ipv6_optlen(hop) : 0);
802+
txopts = ipv6_renew_options(sk, old, IPV6_HOPOPTS, hop);
804803
txopt_put(old);
805804
if (IS_ERR(txopts))
806805
return PTR_ERR(txopts);
@@ -1222,8 +1221,7 @@ static int calipso_req_setattr(struct request_sock *req,
12221221
if (IS_ERR(new))
12231222
return PTR_ERR(new);
12241223

1225-
txopts = ipv6_renew_options_kern(sk, req_inet->ipv6_opt, IPV6_HOPOPTS,
1226-
new, new ? ipv6_optlen(new) : 0);
1224+
txopts = ipv6_renew_options(sk, req_inet->ipv6_opt, IPV6_HOPOPTS, new);
12271225

12281226
kfree(new);
12291227

@@ -1260,8 +1258,7 @@ static void calipso_req_delattr(struct request_sock *req)
12601258
if (calipso_opt_del(req_inet->ipv6_opt->hopopt, &new))
12611259
return; /* Nothing to do */
12621260

1263-
txopts = ipv6_renew_options_kern(sk, req_inet->ipv6_opt, IPV6_HOPOPTS,
1264-
new, new ? ipv6_optlen(new) : 0);
1261+
txopts = ipv6_renew_options(sk, req_inet->ipv6_opt, IPV6_HOPOPTS, new);
12651262

12661263
if (!IS_ERR(txopts)) {
12671264
txopts = xchg(&req_inet->ipv6_opt, txopts);

net/ipv6/exthdrs.c

Lines changed: 30 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,29 +1015,21 @@ ipv6_dup_options(struct sock *sk, struct ipv6_txoptions *opt)
10151015
}
10161016
EXPORT_SYMBOL_GPL(ipv6_dup_options);
10171017

1018-
static int ipv6_renew_option(void *ohdr,
1019-
struct ipv6_opt_hdr __user *newopt, int newoptlen,
1020-
int inherit,
1021-
struct ipv6_opt_hdr **hdr,
1022-
char **p)
1018+
static void ipv6_renew_option(int renewtype,
1019+
struct ipv6_opt_hdr **dest,
1020+
struct ipv6_opt_hdr *old,
1021+
struct ipv6_opt_hdr *new,
1022+
int newtype, char **p)
10231023
{
1024-
if (inherit) {
1025-
if (ohdr) {
1026-
memcpy(*p, ohdr, ipv6_optlen((struct ipv6_opt_hdr *)ohdr));
1027-
*hdr = (struct ipv6_opt_hdr *)*p;
1028-
*p += CMSG_ALIGN(ipv6_optlen(*hdr));
1029-
}
1030-
} else {
1031-
if (newopt) {
1032-
if (copy_from_user(*p, newopt, newoptlen))
1033-
return -EFAULT;
1034-
*hdr = (struct ipv6_opt_hdr *)*p;
1035-
if (ipv6_optlen(*hdr) > newoptlen)
1036-
return -EINVAL;
1037-
*p += CMSG_ALIGN(newoptlen);
1038-
}
1039-
}
1040-
return 0;
1024+
struct ipv6_opt_hdr *src;
1025+
1026+
src = (renewtype == newtype ? new : old);
1027+
if (!src)
1028+
return;
1029+
1030+
memcpy(*p, src, ipv6_optlen(src));
1031+
*dest = (struct ipv6_opt_hdr *)*p;
1032+
*p += CMSG_ALIGN(ipv6_optlen(*dest));
10411033
}
10421034

10431035
/**
@@ -1063,13 +1055,11 @@ static int ipv6_renew_option(void *ohdr,
10631055
*/
10641056
struct ipv6_txoptions *
10651057
ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *opt,
1066-
int newtype,
1067-
struct ipv6_opt_hdr __user *newopt, int newoptlen)
1058+
int newtype, struct ipv6_opt_hdr *newopt)
10681059
{
10691060
int tot_len = 0;
10701061
char *p;
10711062
struct ipv6_txoptions *opt2;
1072-
int err;
10731063

10741064
if (opt) {
10751065
if (newtype != IPV6_HOPOPTS && opt->hopopt)
@@ -1082,8 +1072,8 @@ ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *opt,
10821072
tot_len += CMSG_ALIGN(ipv6_optlen(opt->dst1opt));
10831073
}
10841074

1085-
if (newopt && newoptlen)
1086-
tot_len += CMSG_ALIGN(newoptlen);
1075+
if (newopt)
1076+
tot_len += CMSG_ALIGN(ipv6_optlen(newopt));
10871077

10881078
if (!tot_len)
10891079
return NULL;
@@ -1098,67 +1088,26 @@ ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *opt,
10981088
opt2->tot_len = tot_len;
10991089
p = (char *)(opt2 + 1);
11001090

1101-
err = ipv6_renew_option(opt ? opt->hopopt : NULL, newopt, newoptlen,
1102-
newtype != IPV6_HOPOPTS,
1103-
&opt2->hopopt, &p);
1104-
if (err)
1105-
goto out;
1106-
1107-
err = ipv6_renew_option(opt ? opt->dst0opt : NULL, newopt, newoptlen,
1108-
newtype != IPV6_RTHDRDSTOPTS,
1109-
&opt2->dst0opt, &p);
1110-
if (err)
1111-
goto out;
1112-
1113-
err = ipv6_renew_option(opt ? opt->srcrt : NULL, newopt, newoptlen,
1114-
newtype != IPV6_RTHDR,
1115-
(struct ipv6_opt_hdr **)&opt2->srcrt, &p);
1116-
if (err)
1117-
goto out;
1118-
1119-
err = ipv6_renew_option(opt ? opt->dst1opt : NULL, newopt, newoptlen,
1120-
newtype != IPV6_DSTOPTS,
1121-
&opt2->dst1opt, &p);
1122-
if (err)
1123-
goto out;
1091+
ipv6_renew_option(IPV6_HOPOPTS, &opt2->hopopt,
1092+
(opt ? opt->hopopt : NULL),
1093+
newopt, newtype, &p);
1094+
ipv6_renew_option(IPV6_RTHDRDSTOPTS, &opt2->dst0opt,
1095+
(opt ? opt->dst0opt : NULL),
1096+
newopt, newtype, &p);
1097+
ipv6_renew_option(IPV6_RTHDR,
1098+
(struct ipv6_opt_hdr **)&opt2->srcrt,
1099+
(opt ? (struct ipv6_opt_hdr *)opt->srcrt : NULL),
1100+
newopt, newtype, &p);
1101+
ipv6_renew_option(IPV6_DSTOPTS, &opt2->dst1opt,
1102+
(opt ? opt->dst1opt : NULL),
1103+
newopt, newtype, &p);
11241104

11251105
opt2->opt_nflen = (opt2->hopopt ? ipv6_optlen(opt2->hopopt) : 0) +
11261106
(opt2->dst0opt ? ipv6_optlen(opt2->dst0opt) : 0) +
11271107
(opt2->srcrt ? ipv6_optlen(opt2->srcrt) : 0);
11281108
opt2->opt_flen = (opt2->dst1opt ? ipv6_optlen(opt2->dst1opt) : 0);
11291109

11301110
return opt2;
1131-
out:
1132-
sock_kfree_s(sk, opt2, opt2->tot_len);
1133-
return ERR_PTR(err);
1134-
}
1135-
1136-
/**
1137-
* ipv6_renew_options_kern - replace a specific ext hdr with a new one.
1138-
*
1139-
* @sk: sock from which to allocate memory
1140-
* @opt: original options
1141-
* @newtype: option type to replace in @opt
1142-
* @newopt: new option of type @newtype to replace (kernel-mem)
1143-
* @newoptlen: length of @newopt
1144-
*
1145-
* See ipv6_renew_options(). The difference is that @newopt is
1146-
* kernel memory, rather than user memory.
1147-
*/
1148-
struct ipv6_txoptions *
1149-
ipv6_renew_options_kern(struct sock *sk, struct ipv6_txoptions *opt,
1150-
int newtype, struct ipv6_opt_hdr *newopt,
1151-
int newoptlen)
1152-
{
1153-
struct ipv6_txoptions *ret_val;
1154-
const mm_segment_t old_fs = get_fs();
1155-
1156-
set_fs(KERNEL_DS);
1157-
ret_val = ipv6_renew_options(sk, opt, newtype,
1158-
(struct ipv6_opt_hdr __user *)newopt,
1159-
newoptlen);
1160-
set_fs(old_fs);
1161-
return ret_val;
11621111
}
11631112

11641113
struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space,

net/ipv6/ipv6_sockglue.c

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,12 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
398398
case IPV6_DSTOPTS:
399399
{
400400
struct ipv6_txoptions *opt;
401+
struct ipv6_opt_hdr *new = NULL;
402+
403+
/* hop-by-hop / destination options are privileged option */
404+
retv = -EPERM;
405+
if (optname != IPV6_RTHDR && !ns_capable(net->user_ns, CAP_NET_RAW))
406+
break;
401407

402408
/* remove any sticky options header with a zero option
403409
* length, per RFC3542.
@@ -409,17 +415,22 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
409415
else if (optlen < sizeof(struct ipv6_opt_hdr) ||
410416
optlen & 0x7 || optlen > 8 * 255)
411417
goto e_inval;
412-
413-
/* hop-by-hop / destination options are privileged option */
414-
retv = -EPERM;
415-
if (optname != IPV6_RTHDR && !ns_capable(net->user_ns, CAP_NET_RAW))
416-
break;
418+
else {
419+
new = memdup_user(optval, optlen);
420+
if (IS_ERR(new)) {
421+
retv = PTR_ERR(new);
422+
break;
423+
}
424+
if (unlikely(ipv6_optlen(new) > optlen)) {
425+
kfree(new);
426+
goto e_inval;
427+
}
428+
}
417429

418430
opt = rcu_dereference_protected(np->opt,
419431
lockdep_sock_is_held(sk));
420-
opt = ipv6_renew_options(sk, opt, optname,
421-
(struct ipv6_opt_hdr __user *)optval,
422-
optlen);
432+
opt = ipv6_renew_options(sk, opt, optname, new);
433+
kfree(new);
423434
if (IS_ERR(opt)) {
424435
retv = PTR_ERR(opt);
425436
break;

0 commit comments

Comments
 (0)