Commit message: net/appletalk: fix minor pointer leak to userspace in SIOCFINDIPDDPRT

Fields ->dev and ->next of struct ipddp_route may be copied to
userspace on the SIOCFINDIPDDPRT ioctl. This is only accessible
to CAP_NET_ADMIN though. Let's manually copy the relevant fields
instead of using memcpy().

BugLink: http://blog.infosectcbr.com.au/2018/09/linux-kernel-infoleaks.html
Cc: Jann Horn <jannh@google.com>
Signed-off-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David S. Miller <davem@davemloft.net>
CVE: CVE-2018-20511
CWE: CWE-200
Vulnerability description: An issue was discovered in the Linux kernel before 4.18.11. The ipddp_ioctl function in drivers/net/appletalk/ipddp.c allows local users to obtain sensitive kernel address information by leveraging CAP_NET_ADMIN to read the ipddp_route dev and next fields via an SIOCFINDIPDDPRT ioctl call.

Project: linux
File: ipddp.c

static struct ipddp_route* __ipddp_find_route(struct ipddp_route *rt)
{
        struct ipddp_route *f;

        for(f = ipddp_route_list; f != NULL; f = f->next)
        {
                if(f->ip == rt->ip &&
		   f->at.s_net == rt->at.s_net &&
		   f->at.s_node == rt->at.s_node)
                        return f;
        }

        return NULL;
}

Project: linux
File: ipddp.c

static void __exit ipddp_cleanup_module(void)
{
        struct ipddp_route *p;

	unregister_netdev(dev_ipddp);
        free_netdev(dev_ipddp);

        while (ipddp_route_list) {
                p = ipddp_route_list->next;
                kfree(ipddp_route_list);
                ipddp_route_list = p;
        }
}

Project: linux
File: ipddp.c

static int ipddp_create(struct ipddp_route *new_rt)
{
        struct ipddp_route *rt = kzalloc(sizeof(*rt), GFP_KERNEL);

        if (rt == NULL)
                return -ENOMEM;

        rt->ip = new_rt->ip;
        rt->at = new_rt->at;
        rt->next = NULL;
        if ((rt->dev = atrtr_get_dev(&rt->at)) == NULL) {
		kfree(rt);
                return -ENETUNREACH;
        }

	spin_lock_bh(&ipddp_route_lock);
	if (__ipddp_find_route(rt)) {
		spin_unlock_bh(&ipddp_route_lock);
		kfree(rt);
		return -EEXIST;
	}

        rt->next = ipddp_route_list;
        ipddp_route_list = rt;

	spin_unlock_bh(&ipddp_route_lock);

        return 0;
}

Project: linux
File: ipddp.c

static int ipddp_delete(struct ipddp_route *rt)
{
        struct ipddp_route **r = &ipddp_route_list;
        struct ipddp_route *tmp;

	spin_lock_bh(&ipddp_route_lock);
        while((tmp = *r) != NULL)
        {
                if(tmp->ip == rt->ip &&
		   tmp->at.s_net == rt->at.s_net &&
		   tmp->at.s_node == rt->at.s_node)
                {
                        *r = tmp->next;
			spin_unlock_bh(&ipddp_route_lock);
                        kfree(tmp);
                        return 0;
                }
                r = &tmp->next;
        }

	spin_unlock_bh(&ipddp_route_lock);
        return -ENOENT;
}

Project: linux
File: ipddp.c

static int __init ipddp_init_module(void)
{
	dev_ipddp = ipddp_init();
	return PTR_ERR_OR_ZERO(dev_ipddp);
}

Project: linux
File: ipddp.c

static netdev_tx_t ipddp_xmit(struct sk_buff *skb, struct net_device *dev)
{
	__be32 paddr = skb_rtable(skb)->rt_gateway;
        struct ddpehdr *ddp;
        struct ipddp_route *rt;
        struct atalk_addr *our_addr;

	spin_lock(&ipddp_route_lock);

	/*
         * Find appropriate route to use, based only on IP number.
         */
        for(rt = ipddp_route_list; rt != NULL; rt = rt->next)
        {
                if(rt->ip == paddr)
                        break;
        }
        if(rt == NULL) {
		spin_unlock(&ipddp_route_lock);
                return NETDEV_TX_OK;
	}

        our_addr = atalk_find_dev_addr(rt->dev);

	if(ipddp_mode == IPDDP_DECAP)
		/* 
		 * Pull off the excess room that should not be there.
		 * This is due to a hard-header problem. This is the
		 * quick fix for now though, till it breaks.
		 */
		skb_pull(skb, 35-(sizeof(struct ddpehdr)+1));

	/* Create the Extended DDP header */
	ddp = (struct ddpehdr *)skb->data;
        ddp->deh_len_hops = htons(skb->len + (1<<10));
        ddp->deh_sum = 0;

	/*
         * For Localtalk we need aarp_send_ddp to strip the
         * long DDP header and place a shot DDP header on it.
         */
        if(rt->dev->type == ARPHRD_LOCALTLK)
        {
                ddp->deh_dnet  = 0;   /* FIXME more hops?? */
                ddp->deh_snet  = 0;
        }
        else
        {
                ddp->deh_dnet  = rt->at.s_net;   /* FIXME more hops?? */
                ddp->deh_snet  = our_addr->s_net;
        }
        ddp->deh_dnode = rt->at.s_node;
        ddp->deh_snode = our_addr->s_node;
        ddp->deh_dport = 72;
        ddp->deh_sport = 72;

        *((__u8 *)(ddp+1)) = 22;        	/* ddp type = IP */

        skb->protocol = htons(ETH_P_ATALK);     /* Protocol has changed */

	dev->stats.tx_packets++;
	dev->stats.tx_bytes += skb->len;

	aarp_send_ddp(rt->dev, skb, &rt->at, NULL);

	spin_unlock(&ipddp_route_lock);

        return NETDEV_TX_OK;
}