summaryrefslogtreecommitdiffstats
path: root/net/ipv6/exthdrs.c
diff options
context:
space:
mode:
authorPaul Moore <paul@paul-moore.com>2018-07-04 09:58:05 -0400
committerDavid S. Miller <davem@davemloft.net>2018-07-05 07:15:26 -0400
commita9ba23d48dbc6ffd08426bb10f05720e0b9f5c14 (patch)
tree1b9e5753be930b5709d9180738df65ad2999bbff /net/ipv6/exthdrs.c
parent0df8adbb88db8283f102a2afa91610a502245cb2 (diff)
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 <viro@zeniv.linux.org.uk> Signed-off-by: Paul Moore <paul@paul-moore.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ipv6/exthdrs.c')
-rw-r--r--net/ipv6/exthdrs.c111
1 files changed, 30 insertions, 81 deletions
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index 5bc2bf3733ab..20291c2036fc 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -1015,29 +1015,21 @@ ipv6_dup_options(struct sock *sk, struct ipv6_txoptions *opt)
1015} 1015}
1016EXPORT_SYMBOL_GPL(ipv6_dup_options); 1016EXPORT_SYMBOL_GPL(ipv6_dup_options);
1017 1017
1018static int ipv6_renew_option(void *ohdr, 1018static void ipv6_renew_option(int renewtype,
1019 struct ipv6_opt_hdr __user *newopt, int newoptlen, 1019 struct ipv6_opt_hdr **dest,
1020 int inherit, 1020 struct ipv6_opt_hdr *old,
1021 struct ipv6_opt_hdr **hdr, 1021 struct ipv6_opt_hdr *new,
1022 char **p) 1022 int newtype, char **p)
1023{ 1023{
1024 if (inherit) { 1024 struct ipv6_opt_hdr *src;
1025 if (ohdr) { 1025
1026 memcpy(*p, ohdr, ipv6_optlen((struct ipv6_opt_hdr *)ohdr)); 1026 src = (renewtype == newtype ? new : old);
1027 *hdr = (struct ipv6_opt_hdr *)*p; 1027 if (!src)
1028 *p += CMSG_ALIGN(ipv6_optlen(*hdr)); 1028 return;
1029 } 1029
1030 } else { 1030 memcpy(*p, src, ipv6_optlen(src));
1031 if (newopt) { 1031 *dest = (struct ipv6_opt_hdr *)*p;
1032 if (copy_from_user(*p, newopt, newoptlen)) 1032 *p += CMSG_ALIGN(ipv6_optlen(*dest));
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;
1041} 1033}
1042 1034
1043/** 1035/**
@@ -1063,13 +1055,11 @@ static int ipv6_renew_option(void *ohdr,
1063 */ 1055 */
1064struct ipv6_txoptions * 1056struct ipv6_txoptions *
1065ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *opt, 1057ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *opt,
1066 int newtype, 1058 int newtype, struct ipv6_opt_hdr *newopt)
1067 struct ipv6_opt_hdr __user *newopt, int newoptlen)
1068{ 1059{
1069 int tot_len = 0; 1060 int tot_len = 0;
1070 char *p; 1061 char *p;
1071 struct ipv6_txoptions *opt2; 1062 struct ipv6_txoptions *opt2;
1072 int err;
1073 1063
1074 if (opt) { 1064 if (opt) {
1075 if (newtype != IPV6_HOPOPTS && opt->hopopt) 1065 if (newtype != IPV6_HOPOPTS && opt->hopopt)
@@ -1082,8 +1072,8 @@ ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *opt,
1082 tot_len += CMSG_ALIGN(ipv6_optlen(opt->dst1opt)); 1072 tot_len += CMSG_ALIGN(ipv6_optlen(opt->dst1opt));
1083 } 1073 }
1084 1074
1085 if (newopt && newoptlen) 1075 if (newopt)
1086 tot_len += CMSG_ALIGN(newoptlen); 1076 tot_len += CMSG_ALIGN(ipv6_optlen(newopt));
1087 1077
1088 if (!tot_len) 1078 if (!tot_len)
1089 return NULL; 1079 return NULL;
@@ -1098,29 +1088,19 @@ ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *opt,
1098 opt2->tot_len = tot_len; 1088 opt2->tot_len = tot_len;
1099 p = (char *)(opt2 + 1); 1089 p = (char *)(opt2 + 1);
1100 1090
1101 err = ipv6_renew_option(opt ? opt->hopopt : NULL, newopt, newoptlen, 1091 ipv6_renew_option(IPV6_HOPOPTS, &opt2->hopopt,
1102 newtype != IPV6_HOPOPTS, 1092 (opt ? opt->hopopt : NULL),
1103 &opt2->hopopt, &p); 1093 newopt, newtype, &p);
1104 if (err) 1094 ipv6_renew_option(IPV6_RTHDRDSTOPTS, &opt2->dst0opt,
1105 goto out; 1095 (opt ? opt->dst0opt : NULL),
1106 1096 newopt, newtype, &p);
1107 err = ipv6_renew_option(opt ? opt->dst0opt : NULL, newopt, newoptlen, 1097 ipv6_renew_option(IPV6_RTHDR,
1108 newtype != IPV6_RTHDRDSTOPTS, 1098 (struct ipv6_opt_hdr **)&opt2->srcrt,
1109 &opt2->dst0opt, &p); 1099 (opt ? (struct ipv6_opt_hdr *)opt->srcrt : NULL),
1110 if (err) 1100 newopt, newtype, &p);
1111 goto out; 1101 ipv6_renew_option(IPV6_DSTOPTS, &opt2->dst1opt,
1112 1102 (opt ? opt->dst1opt : NULL),
1113 err = ipv6_renew_option(opt ? opt->srcrt : NULL, newopt, newoptlen, 1103 newopt, newtype, &p);
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;
1124 1104
1125 opt2->opt_nflen = (opt2->hopopt ? ipv6_optlen(opt2->hopopt) : 0) + 1105 opt2->opt_nflen = (opt2->hopopt ? ipv6_optlen(opt2->hopopt) : 0) +
1126 (opt2->dst0opt ? ipv6_optlen(opt2->dst0opt) : 0) + 1106 (opt2->dst0opt ? ipv6_optlen(opt2->dst0opt) : 0) +
@@ -1128,37 +1108,6 @@ ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *opt,
1128 opt2->opt_flen = (opt2->dst1opt ? ipv6_optlen(opt2->dst1opt) : 0); 1108 opt2->opt_flen = (opt2->dst1opt ? ipv6_optlen(opt2->dst1opt) : 0);
1129 1109
1130 return opt2; 1110 return opt2;
1131out:
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 */
1148struct ipv6_txoptions *
1149ipv6_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;
1162} 1111}
1163 1112
1164struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space, 1113struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space,