diff options
author | Ben Hutchings <bhutchings@solarflare.com> | 2011-03-17 03:34:32 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2011-03-18 18:13:11 -0400 |
commit | 3a7da39d165e0c363c294feec119db1427032afd (patch) | |
tree | 901e54990fdd90d6f90d27533c36e61858216e34 | |
parent | 5e5069b41d5b82bcadc1dbf73f48476b428c102f (diff) |
ethtool: Compat handling for struct ethtool_rxnfc
This structure was accidentally defined such that its layout can
differ between 32-bit and 64-bit processes. Add compat structure
definitions and an ioctl wrapper function.
Signed-off-by: Ben Hutchings <bhutchings@solarflare.com>
Acked-by: Alexander Duyck <alexander.h.duyck@intel.com>
Cc: stable@kernel.org [2.6.30+]
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/linux/ethtool.h | 34 | ||||
-rw-r--r-- | net/socket.c | 114 |
2 files changed, 141 insertions, 7 deletions
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index aac3e2eeb4fd..b297f288f6eb 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h | |||
@@ -13,6 +13,9 @@ | |||
13 | #ifndef _LINUX_ETHTOOL_H | 13 | #ifndef _LINUX_ETHTOOL_H |
14 | #define _LINUX_ETHTOOL_H | 14 | #define _LINUX_ETHTOOL_H |
15 | 15 | ||
16 | #ifdef __KERNEL__ | ||
17 | #include <linux/compat.h> | ||
18 | #endif | ||
16 | #include <linux/types.h> | 19 | #include <linux/types.h> |
17 | #include <linux/if_ether.h> | 20 | #include <linux/if_ether.h> |
18 | 21 | ||
@@ -450,6 +453,37 @@ struct ethtool_rxnfc { | |||
450 | __u32 rule_locs[0]; | 453 | __u32 rule_locs[0]; |
451 | }; | 454 | }; |
452 | 455 | ||
456 | #ifdef __KERNEL__ | ||
457 | #ifdef CONFIG_COMPAT | ||
458 | |||
459 | struct compat_ethtool_rx_flow_spec { | ||
460 | u32 flow_type; | ||
461 | union { | ||
462 | struct ethtool_tcpip4_spec tcp_ip4_spec; | ||
463 | struct ethtool_tcpip4_spec udp_ip4_spec; | ||
464 | struct ethtool_tcpip4_spec sctp_ip4_spec; | ||
465 | struct ethtool_ah_espip4_spec ah_ip4_spec; | ||
466 | struct ethtool_ah_espip4_spec esp_ip4_spec; | ||
467 | struct ethtool_usrip4_spec usr_ip4_spec; | ||
468 | struct ethhdr ether_spec; | ||
469 | u8 hdata[72]; | ||
470 | } h_u, m_u; | ||
471 | compat_u64 ring_cookie; | ||
472 | u32 location; | ||
473 | }; | ||
474 | |||
475 | struct compat_ethtool_rxnfc { | ||
476 | u32 cmd; | ||
477 | u32 flow_type; | ||
478 | compat_u64 data; | ||
479 | struct compat_ethtool_rx_flow_spec fs; | ||
480 | u32 rule_cnt; | ||
481 | u32 rule_locs[0]; | ||
482 | }; | ||
483 | |||
484 | #endif /* CONFIG_COMPAT */ | ||
485 | #endif /* __KERNEL__ */ | ||
486 | |||
453 | /** | 487 | /** |
454 | * struct ethtool_rxfh_indir - command to get or set RX flow hash indirection | 488 | * struct ethtool_rxfh_indir - command to get or set RX flow hash indirection |
455 | * @cmd: Specific command number - %ETHTOOL_GRXFHINDIR or %ETHTOOL_SRXFHINDIR | 489 | * @cmd: Specific command number - %ETHTOOL_GRXFHINDIR or %ETHTOOL_SRXFHINDIR |
diff --git a/net/socket.c b/net/socket.c index 937d0fcf74bc..5212447c86e7 100644 --- a/net/socket.c +++ b/net/socket.c | |||
@@ -2588,23 +2588,123 @@ static int dev_ifconf(struct net *net, struct compat_ifconf __user *uifc32) | |||
2588 | 2588 | ||
2589 | static int ethtool_ioctl(struct net *net, struct compat_ifreq __user *ifr32) | 2589 | static int ethtool_ioctl(struct net *net, struct compat_ifreq __user *ifr32) |
2590 | { | 2590 | { |
2591 | struct compat_ethtool_rxnfc __user *compat_rxnfc; | ||
2592 | bool convert_in = false, convert_out = false; | ||
2593 | size_t buf_size = ALIGN(sizeof(struct ifreq), 8); | ||
2594 | struct ethtool_rxnfc __user *rxnfc; | ||
2591 | struct ifreq __user *ifr; | 2595 | struct ifreq __user *ifr; |
2596 | u32 rule_cnt = 0, actual_rule_cnt; | ||
2597 | u32 ethcmd; | ||
2592 | u32 data; | 2598 | u32 data; |
2593 | void __user *datap; | 2599 | int ret; |
2600 | |||
2601 | if (get_user(data, &ifr32->ifr_ifru.ifru_data)) | ||
2602 | return -EFAULT; | ||
2594 | 2603 | ||
2595 | ifr = compat_alloc_user_space(sizeof(*ifr)); | 2604 | compat_rxnfc = compat_ptr(data); |
2596 | 2605 | ||
2597 | if (copy_in_user(&ifr->ifr_name, &ifr32->ifr_name, IFNAMSIZ)) | 2606 | if (get_user(ethcmd, &compat_rxnfc->cmd)) |
2598 | return -EFAULT; | 2607 | return -EFAULT; |
2599 | 2608 | ||
2600 | if (get_user(data, &ifr32->ifr_ifru.ifru_data)) | 2609 | /* Most ethtool structures are defined without padding. |
2610 | * Unfortunately struct ethtool_rxnfc is an exception. | ||
2611 | */ | ||
2612 | switch (ethcmd) { | ||
2613 | default: | ||
2614 | break; | ||
2615 | case ETHTOOL_GRXCLSRLALL: | ||
2616 | /* Buffer size is variable */ | ||
2617 | if (get_user(rule_cnt, &compat_rxnfc->rule_cnt)) | ||
2618 | return -EFAULT; | ||
2619 | if (rule_cnt > KMALLOC_MAX_SIZE / sizeof(u32)) | ||
2620 | return -ENOMEM; | ||
2621 | buf_size += rule_cnt * sizeof(u32); | ||
2622 | /* fall through */ | ||
2623 | case ETHTOOL_GRXRINGS: | ||
2624 | case ETHTOOL_GRXCLSRLCNT: | ||
2625 | case ETHTOOL_GRXCLSRULE: | ||
2626 | convert_out = true; | ||
2627 | /* fall through */ | ||
2628 | case ETHTOOL_SRXCLSRLDEL: | ||
2629 | case ETHTOOL_SRXCLSRLINS: | ||
2630 | buf_size += sizeof(struct ethtool_rxnfc); | ||
2631 | convert_in = true; | ||
2632 | break; | ||
2633 | } | ||
2634 | |||
2635 | ifr = compat_alloc_user_space(buf_size); | ||
2636 | rxnfc = (void *)ifr + ALIGN(sizeof(struct ifreq), 8); | ||
2637 | |||
2638 | if (copy_in_user(&ifr->ifr_name, &ifr32->ifr_name, IFNAMSIZ)) | ||
2601 | return -EFAULT; | 2639 | return -EFAULT; |
2602 | 2640 | ||
2603 | datap = compat_ptr(data); | 2641 | if (put_user(convert_in ? rxnfc : compat_ptr(data), |
2604 | if (put_user(datap, &ifr->ifr_ifru.ifru_data)) | 2642 | &ifr->ifr_ifru.ifru_data)) |
2605 | return -EFAULT; | 2643 | return -EFAULT; |
2606 | 2644 | ||
2607 | return dev_ioctl(net, SIOCETHTOOL, ifr); | 2645 | if (convert_in) { |
2646 | /* We expect there to be holes between fs.m_u and | ||
2647 | * fs.ring_cookie and at the end of fs, but nowhere else. | ||
2648 | */ | ||
2649 | BUILD_BUG_ON(offsetof(struct compat_ethtool_rxnfc, fs.m_u) + | ||
2650 | sizeof(compat_rxnfc->fs.m_u) != | ||
2651 | offsetof(struct ethtool_rxnfc, fs.m_u) + | ||
2652 | sizeof(rxnfc->fs.m_u)); | ||
2653 | BUILD_BUG_ON( | ||
2654 | offsetof(struct compat_ethtool_rxnfc, fs.location) - | ||
2655 | offsetof(struct compat_ethtool_rxnfc, fs.ring_cookie) != | ||
2656 | offsetof(struct ethtool_rxnfc, fs.location) - | ||
2657 | offsetof(struct ethtool_rxnfc, fs.ring_cookie)); | ||
2658 | |||
2659 | if (copy_in_user(rxnfc, compat_rxnfc, | ||
2660 | (void *)(&rxnfc->fs.m_u + 1) - | ||
2661 | (void *)rxnfc) || | ||
2662 | copy_in_user(&rxnfc->fs.ring_cookie, | ||
2663 | &compat_rxnfc->fs.ring_cookie, | ||
2664 | (void *)(&rxnfc->fs.location + 1) - | ||
2665 | (void *)&rxnfc->fs.ring_cookie) || | ||
2666 | copy_in_user(&rxnfc->rule_cnt, &compat_rxnfc->rule_cnt, | ||
2667 | sizeof(rxnfc->rule_cnt))) | ||
2668 | return -EFAULT; | ||
2669 | } | ||
2670 | |||
2671 | ret = dev_ioctl(net, SIOCETHTOOL, ifr); | ||
2672 | if (ret) | ||
2673 | return ret; | ||
2674 | |||
2675 | if (convert_out) { | ||
2676 | if (copy_in_user(compat_rxnfc, rxnfc, | ||
2677 | (const void *)(&rxnfc->fs.m_u + 1) - | ||
2678 | (const void *)rxnfc) || | ||
2679 | copy_in_user(&compat_rxnfc->fs.ring_cookie, | ||
2680 | &rxnfc->fs.ring_cookie, | ||
2681 | (const void *)(&rxnfc->fs.location + 1) - | ||
2682 | (const void *)&rxnfc->fs.ring_cookie) || | ||
2683 | copy_in_user(&compat_rxnfc->rule_cnt, &rxnfc->rule_cnt, | ||
2684 | sizeof(rxnfc->rule_cnt))) | ||
2685 | return -EFAULT; | ||
2686 | |||
2687 | if (ethcmd == ETHTOOL_GRXCLSRLALL) { | ||
2688 | /* As an optimisation, we only copy the actual | ||
2689 | * number of rules that the underlying | ||
2690 | * function returned. Since Mallory might | ||
2691 | * change the rule count in user memory, we | ||
2692 | * check that it is less than the rule count | ||
2693 | * originally given (as the user buffer size), | ||
2694 | * which has been range-checked. | ||
2695 | */ | ||
2696 | if (get_user(actual_rule_cnt, &rxnfc->rule_cnt)) | ||
2697 | return -EFAULT; | ||
2698 | if (actual_rule_cnt < rule_cnt) | ||
2699 | rule_cnt = actual_rule_cnt; | ||
2700 | if (copy_in_user(&compat_rxnfc->rule_locs[0], | ||
2701 | &rxnfc->rule_locs[0], | ||
2702 | rule_cnt * sizeof(u32))) | ||
2703 | return -EFAULT; | ||
2704 | } | ||
2705 | } | ||
2706 | |||
2707 | return 0; | ||
2608 | } | 2708 | } |
2609 | 2709 | ||
2610 | static int compat_siocwandev(struct net *net, struct compat_ifreq __user *uifr32) | 2710 | static int compat_siocwandev(struct net *net, struct compat_ifreq __user *uifr32) |