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>

authored by Ben Hutchings and committed by David S. Miller 3a7da39d 5e5069b4

+143 -9
+34
include/linux/ethtool.h
··· 13 13 #ifndef _LINUX_ETHTOOL_H 14 14 #define _LINUX_ETHTOOL_H 15 15 16 + #ifdef __KERNEL__ 17 + #include <linux/compat.h> 18 + #endif 16 19 #include <linux/types.h> 17 20 #include <linux/if_ether.h> 18 21 ··· 452 449 __u32 rule_cnt; 453 450 __u32 rule_locs[0]; 454 451 }; 452 + 453 + #ifdef __KERNEL__ 454 + #ifdef CONFIG_COMPAT 455 + 456 + struct compat_ethtool_rx_flow_spec { 457 + u32 flow_type; 458 + union { 459 + struct ethtool_tcpip4_spec tcp_ip4_spec; 460 + struct ethtool_tcpip4_spec udp_ip4_spec; 461 + struct ethtool_tcpip4_spec sctp_ip4_spec; 462 + struct ethtool_ah_espip4_spec ah_ip4_spec; 463 + struct ethtool_ah_espip4_spec esp_ip4_spec; 464 + struct ethtool_usrip4_spec usr_ip4_spec; 465 + struct ethhdr ether_spec; 466 + u8 hdata[72]; 467 + } h_u, m_u; 468 + compat_u64 ring_cookie; 469 + u32 location; 470 + }; 471 + 472 + struct compat_ethtool_rxnfc { 473 + u32 cmd; 474 + u32 flow_type; 475 + compat_u64 data; 476 + struct compat_ethtool_rx_flow_spec fs; 477 + u32 rule_cnt; 478 + u32 rule_locs[0]; 479 + }; 480 + 481 + #endif /* CONFIG_COMPAT */ 482 + #endif /* __KERNEL__ */ 455 483 456 484 /** 457 485 * struct ethtool_rxfh_indir - command to get or set RX flow hash indirection
+109 -9
net/socket.c
··· 2588 2588 2589 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 2595 struct ifreq __user *ifr; 2596 + u32 rule_cnt = 0, actual_rule_cnt; 2597 + u32 ethcmd; 2592 2598 u32 data; 2593 - void __user *datap; 2594 - 2595 - ifr = compat_alloc_user_space(sizeof(*ifr)); 2596 - 2597 - if (copy_in_user(&ifr->ifr_name, &ifr32->ifr_name, IFNAMSIZ)) 2598 - return -EFAULT; 2599 + int ret; 2599 2600 2600 2601 if (get_user(data, &ifr32->ifr_ifru.ifru_data)) 2601 2602 return -EFAULT; 2602 2603 2603 - datap = compat_ptr(data); 2604 - if (put_user(datap, &ifr->ifr_ifru.ifru_data)) 2604 + compat_rxnfc = compat_ptr(data); 2605 + 2606 + if (get_user(ethcmd, &compat_rxnfc->cmd)) 2605 2607 return -EFAULT; 2606 2608 2607 - return dev_ioctl(net, SIOCETHTOOL, ifr); 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)) 2639 + return -EFAULT; 2640 + 2641 + if (put_user(convert_in ? rxnfc : compat_ptr(data), 2642 + &ifr->ifr_ifru.ifru_data)) 2643 + return -EFAULT; 2644 + 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 2710 static int compat_siocwandev(struct net *net, struct compat_ifreq __user *uifr32)