Commit message: [NETLINK]: Missing initializations in dumped data

Mostly missing initialization of padding fields of 1 or 2 bytes length,
two instances of uninitialized nlmsgerr->msg of 16 bytes length.

Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
CVE: CVE-2005-4881
CWE: CWE-200
Vulnerability description: The netlink subsystem in the Linux kernel 2.4.x before 2.4.37.6 and 2.6.x before 2.6.13-rc1 does not initialize certain padding fields in structures, which might allow local users to obtain sensitive information from kernel memory via unspecified vectors, related to the (1) tc_fill_qdisc, (2) tcf_fill_node, (3) neightbl_fill_info, (4) neightbl_fill_param_info, (5) neigh_fill_info, (6) rtnetlink_fill_ifinfo, (7) rtnetlink_fill_iwinfo, (8) vif_delete, (9) ipmr_destroy_unres, (10) ipmr_cache_alloc_unres, (11) ipmr_cache_resolve, (12) inet6_fill_ifinfo, (13) tca_get_fill, (14) tca_action_flush, (15) tcf_add_notify, (16) tc_dump_action, (17) cbq_dump_police, (18) __nlmsg_put, (19) __rta_fill, (20) __rta_reserve, (21) inet6_fill_prefix, (22) rsvp_dump, and (23) cbq_dump_ovl functions.

Project: linux-2.6

static int neightbl_fill_info(struct neigh_table *tbl, struct sk_buff *skb,
			      struct netlink_callback *cb)
{
	struct nlmsghdr *nlh;
	struct ndtmsg *ndtmsg;

	nlh = NLMSG_NEW_ANSWER(skb, cb, RTM_NEWNEIGHTBL, sizeof(struct ndtmsg),
			       NLM_F_MULTI);

	ndtmsg = NLMSG_DATA(nlh);

	read_lock_bh(&tbl->lock);
	ndtmsg->ndtm_family = tbl->family;

	RTA_PUT_STRING(skb, NDTA_NAME, tbl->id);
	RTA_PUT_MSECS(skb, NDTA_GC_INTERVAL, tbl->gc_interval);
	RTA_PUT_U32(skb, NDTA_THRESH1, tbl->gc_thresh1);
	RTA_PUT_U32(skb, NDTA_THRESH2, tbl->gc_thresh2);
	RTA_PUT_U32(skb, NDTA_THRESH3, tbl->gc_thresh3);

	{
		unsigned long now = jiffies;
		unsigned int flush_delta = now - tbl->last_flush;
		unsigned int rand_delta = now - tbl->last_rand;

		struct ndt_config ndc = {
			.ndtc_key_len		= tbl->key_len,
			.ndtc_entry_size	= tbl->entry_size,
			.ndtc_entries		= atomic_read(&tbl->entries),
			.ndtc_last_flush	= jiffies_to_msecs(flush_delta),
			.ndtc_last_rand		= jiffies_to_msecs(rand_delta),
			.ndtc_hash_rnd		= tbl->hash_rnd,
			.ndtc_hash_mask		= tbl->hash_mask,
			.ndtc_hash_chain_gc	= tbl->hash_chain_gc,
			.ndtc_proxy_qlen	= tbl->proxy_queue.qlen,
		};

		RTA_PUT(skb, NDTA_CONFIG, sizeof(ndc), &ndc);
	}

	{
		int cpu;
		struct ndt_stats ndst;

		memset(&ndst, 0, sizeof(ndst));

		for (cpu = 0; cpu < NR_CPUS; cpu++) {
			struct neigh_statistics	*st;

			if (!cpu_possible(cpu))
				continue;

			st = per_cpu_ptr(tbl->stats, cpu);
			ndst.ndts_allocs		+= st->allocs;
			ndst.ndts_destroys		+= st->destroys;
			ndst.ndts_hash_grows		+= st->hash_grows;
			ndst.ndts_res_failed		+= st->res_failed;
			ndst.ndts_lookups		+= st->lookups;
			ndst.ndts_hits			+= st->hits;
			ndst.ndts_rcv_probes_mcast	+= st->rcv_probes_mcast;
			ndst.ndts_rcv_probes_ucast	+= st->rcv_probes_ucast;
			ndst.ndts_periodic_gc_runs	+= st->periodic_gc_runs;
			ndst.ndts_forced_gc_runs	+= st->forced_gc_runs;
		}

		RTA_PUT(skb, NDTA_STATS, sizeof(ndst), &ndst);
	}

	BUG_ON(tbl->parms.dev);
	if (neightbl_fill_parms(skb, &tbl->parms) < 0)
		goto rtattr_failure;

	read_unlock_bh(&tbl->lock);
	return NLMSG_END(skb, nlh);

rtattr_failure:
	read_unlock_bh(&tbl->lock);
	return NLMSG_CANCEL(skb, nlh);
 
nlmsg_failure:
	return -1;
}

Project: linux-2.6

static void ipmr_destroy_unres(struct mfc_cache *c)
{
	struct sk_buff *skb;

	atomic_dec(&cache_resolve_queue_len);

	while((skb=skb_dequeue(&c->mfc_un.unres.unresolved))) {
		if (skb->nh.iph->version == 0) {
			struct nlmsghdr *nlh = (struct nlmsghdr *)skb_pull(skb, sizeof(struct iphdr));
			nlh->nlmsg_type = NLMSG_ERROR;
			nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
			skb_trim(skb, nlh->nlmsg_len);
			((struct nlmsgerr*)NLMSG_DATA(nlh))->error = -ETIMEDOUT;
			netlink_unicast(rtnl, skb, NETLINK_CB(skb).dst_pid, MSG_DONTWAIT);
		} else
			kfree_skb(skb);
	}

	kmem_cache_free(mrt_cachep, c);
}

Project: linux-2.6

static void ipmr_cache_resolve(struct mfc_cache *uc, struct mfc_cache *c)
{
	struct sk_buff *skb;

	/*
	 *	Play the pending entries through our router
	 */

	while((skb=__skb_dequeue(&uc->mfc_un.unres.unresolved))) {
		if (skb->nh.iph->version == 0) {
			int err;
			struct nlmsghdr *nlh = (struct nlmsghdr *)skb_pull(skb, sizeof(struct iphdr));

			if (ipmr_fill_mroute(skb, c, NLMSG_DATA(nlh)) > 0) {
				nlh->nlmsg_len = skb->tail - (u8*)nlh;
			} else {
				nlh->nlmsg_type = NLMSG_ERROR;
				nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
				skb_trim(skb, nlh->nlmsg_len);
				((struct nlmsgerr*)NLMSG_DATA(nlh))->error = -EMSGSIZE;
			}
			err = netlink_unicast(rtnl, skb, NETLINK_CB(skb).dst_pid, MSG_DONTWAIT);
		} else
			ip_mr_forward(skb, c, 0);
	}
}

Project: linux-2.6

static int inet6_fill_ifinfo(struct sk_buff *skb, struct inet6_dev *idev, 
			     u32 pid, u32 seq, int event, unsigned int flags)
{
	struct net_device	*dev = idev->dev;
	__s32			*array = NULL;
	struct ifinfomsg	*r;
	struct nlmsghdr 	*nlh;
	unsigned char		*b = skb->tail;
	struct rtattr		*subattr;
	__u32			mtu = dev->mtu;
	struct ifla_cacheinfo	ci;

	nlh = NLMSG_NEW(skb, pid, seq, event, sizeof(*r), flags);
	r = NLMSG_DATA(nlh);
	r->ifi_family = AF_INET6;
	r->ifi_type = dev->type;
	r->ifi_index = dev->ifindex;
	r->ifi_flags = dev_get_flags(dev);
	r->ifi_change = 0;

	RTA_PUT(skb, IFLA_IFNAME, strlen(dev->name)+1, dev->name);

	if (dev->addr_len)
		RTA_PUT(skb, IFLA_ADDRESS, dev->addr_len, dev->dev_addr);

	RTA_PUT(skb, IFLA_MTU, sizeof(mtu), &mtu);
	if (dev->ifindex != dev->iflink)
		RTA_PUT(skb, IFLA_LINK, sizeof(int), &dev->iflink);
			
	subattr = (struct rtattr*)skb->tail;

	RTA_PUT(skb, IFLA_PROTINFO, 0, NULL);

	/* return the device flags */
	RTA_PUT(skb, IFLA_INET6_FLAGS, sizeof(__u32), &idev->if_flags);

	/* return interface cacheinfo */
	ci.max_reasm_len = IPV6_MAXPLEN;
	ci.tstamp = (__u32)(TIME_DELTA(idev->tstamp, INITIAL_JIFFIES) / HZ * 100
		    + TIME_DELTA(idev->tstamp, INITIAL_JIFFIES) % HZ * 100 / HZ);
	ci.reachable_time = idev->nd_parms->reachable_time;
	ci.retrans_time = idev->nd_parms->retrans_time;
	RTA_PUT(skb, IFLA_INET6_CACHEINFO, sizeof(ci), &ci);
	
	/* return the device sysctl params */
	if ((array = kmalloc(DEVCONF_MAX * sizeof(*array), GFP_ATOMIC)) == NULL)
		goto rtattr_failure;
	ipv6_store_devconf(&idev->cnf, array, DEVCONF_MAX * sizeof(*array));
	RTA_PUT(skb, IFLA_INET6_CONF, DEVCONF_MAX * sizeof(*array), array);

	/* XXX - Statistics/MC not implemented */
	subattr->rta_len = skb->tail - (u8*)subattr;

	nlh->nlmsg_len = skb->tail - b;
	kfree(array);
	return skb->len;

nlmsg_failure:
rtattr_failure:
	if (array)
		kfree(array);
	skb_trim(skb, b - skb->data);
	return -1;
}

Project: linux-2.6

static int tcf_add_notify(struct tc_action *a, u32 pid, u32 seq, int event,
                          u16 flags)
{
	struct tcamsg *t;
	struct nlmsghdr *nlh;
	struct sk_buff *skb;
	struct rtattr *x;
	unsigned char *b;
	int err = 0;

	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
	if (!skb)
		return -ENOBUFS;

	b = (unsigned char *)skb->tail;

	nlh = NLMSG_NEW(skb, pid, seq, event, sizeof(*t), flags);
	t = NLMSG_DATA(nlh);
	t->tca_family = AF_UNSPEC;
	
	x = (struct rtattr*) skb->tail;
	RTA_PUT(skb, TCA_ACT_TAB, 0, NULL);

	if (tcf_action_dump(skb, a, 0, 0) < 0)
		goto rtattr_failure;

	x->rta_len = skb->tail - (u8*)x;
	
	nlh->nlmsg_len = skb->tail - b;
	NETLINK_CB(skb).dst_groups = RTMGRP_TC;
	
	err = rtnetlink_send(skb, pid, RTMGRP_TC, flags&NLM_F_ECHO);
	if (err > 0)
		err = 0;
	return err;

rtattr_failure:
nlmsg_failure:
	skb_trim(skb, b - skb->data);
	return -1;
}

Project: linux-2.6

static __inline__ int cbq_dump_police(struct sk_buff *skb, struct cbq_class *cl)
{
	unsigned char	 *b = skb->tail;
	struct tc_cbq_police opt;

	if (cl->police) {
		opt.police = cl->police;
		RTA_PUT(skb, TCA_CBQ_POLICE, sizeof(opt), &opt);
	}
	return skb->len;

rtattr_failure:
	skb_trim(skb, b - skb->data);
	return -1;
}

Project: linux-2.6

tca_get_fill(struct sk_buff *skb, struct tc_action *a, u32 pid, u32 seq,
             u16 flags, int event, int bind, int ref)
{
	struct tcamsg *t;
	struct nlmsghdr *nlh;
	unsigned char *b = skb->tail;
	struct rtattr *x;

	nlh = NLMSG_NEW(skb, pid, seq, event, sizeof(*t), flags);

	t = NLMSG_DATA(nlh);
	t->tca_family = AF_UNSPEC;
	
	x = (struct rtattr*) skb->tail;
	RTA_PUT(skb, TCA_ACT_TAB, 0, NULL);

	if (tcf_action_dump(skb, a, bind, ref) < 0)
		goto rtattr_failure;

	x->rta_len = skb->tail - (u8*)x;
	
	nlh->nlmsg_len = skb->tail - b;
	return skb->len;

rtattr_failure:
nlmsg_failure:
	skb_trim(skb, b - skb->data);
	return -1;
}

Project: linux-2.6

tc_dump_action(struct sk_buff *skb, struct netlink_callback *cb)
{
	struct nlmsghdr *nlh;
	unsigned char *b = skb->tail;
	struct rtattr *x;
	struct tc_action_ops *a_o;
	struct tc_action a;
	int ret = 0;
	struct tcamsg *t = (struct tcamsg *) NLMSG_DATA(cb->nlh);
	char *kind = find_dump_kind(cb->nlh);

	if (kind == NULL) {
		printk("tc_dump_action: action bad kind\n");
		return 0;
	}

	a_o = tc_lookup_action_n(kind);
	if (a_o == NULL) {
		printk("failed to find %s\n", kind);
		return 0;
	}

	memset(&a, 0, sizeof(struct tc_action));
	a.ops = a_o;

	if (a_o->walk == NULL) {
		printk("tc_dump_action: %s !capable of dumping table\n", kind);
		goto rtattr_failure;
	}

	nlh = NLMSG_PUT(skb, NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq,
	                cb->nlh->nlmsg_type, sizeof(*t));
	t = NLMSG_DATA(nlh);
	t->tca_family = AF_UNSPEC;

	x = (struct rtattr *) skb->tail;
	RTA_PUT(skb, TCA_ACT_TAB, 0, NULL);

	ret = a_o->walk(skb, cb, RTM_GETACTION, &a);
	if (ret < 0)
		goto rtattr_failure;

	if (ret > 0) {
		x->rta_len = skb->tail - (u8 *) x;
		ret = skb->len;
	} else
		skb_trim(skb, (u8*)x - skb->data);

	nlh->nlmsg_len = skb->tail - b;
	if (NETLINK_CB(cb->skb).pid && ret)
		nlh->nlmsg_flags |= NLM_F_MULTI;
	module_put(a_o->owner);
	return skb->len;

rtattr_failure:
nlmsg_failure:
	module_put(a_o->owner);
	skb_trim(skb, b - skb->data);
	return skb->len;
}

Project: linux-2.6

static int rtnetlink_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
				 int type, u32 pid, u32 seq, u32 change, 
				 unsigned int flags)
{
	struct ifinfomsg *r;
	struct nlmsghdr  *nlh;
	unsigned char	 *b = skb->tail;

	nlh = NLMSG_NEW(skb, pid, seq, type, sizeof(*r), flags);
	r = NLMSG_DATA(nlh);
	r->ifi_family = AF_UNSPEC;
	r->ifi_type = dev->type;
	r->ifi_index = dev->ifindex;
	r->ifi_flags = dev_get_flags(dev);
	r->ifi_change = change;

	RTA_PUT(skb, IFLA_IFNAME, strlen(dev->name)+1, dev->name);

	if (1) {
		u32 txqlen = dev->tx_queue_len;
		RTA_PUT(skb, IFLA_TXQLEN, sizeof(txqlen), &txqlen);
	}

	if (1) {
		u32 weight = dev->weight;
		RTA_PUT(skb, IFLA_WEIGHT, sizeof(weight), &weight);
	}

	if (1) {
		struct rtnl_link_ifmap map = {
			.mem_start   = dev->mem_start,
			.mem_end     = dev->mem_end,
			.base_addr   = dev->base_addr,
			.irq         = dev->irq,
			.dma         = dev->dma,
			.port        = dev->if_port,
		};
		RTA_PUT(skb, IFLA_MAP, sizeof(map), &map);
	}

	if (dev->addr_len) {
		RTA_PUT(skb, IFLA_ADDRESS, dev->addr_len, dev->dev_addr);
		RTA_PUT(skb, IFLA_BROADCAST, dev->addr_len, dev->broadcast);
	}

	if (1) {
		u32 mtu = dev->mtu;
		RTA_PUT(skb, IFLA_MTU, sizeof(mtu), &mtu);
	}

	if (dev->ifindex != dev->iflink) {
		u32 iflink = dev->iflink;
		RTA_PUT(skb, IFLA_LINK, sizeof(iflink), &iflink);
	}

	if (dev->qdisc_sleeping)
		RTA_PUT(skb, IFLA_QDISC,
			strlen(dev->qdisc_sleeping->ops->id) + 1,
			dev->qdisc_sleeping->ops->id);
	
	if (dev->master) {
		u32 master = dev->master->ifindex;
		RTA_PUT(skb, IFLA_MASTER, sizeof(master), &master);
	}

	if (dev->get_stats) {
		unsigned long *stats = (unsigned long*)dev->get_stats(dev);
		if (stats) {
			struct rtattr  *a;
			__u32	       *s;
			int		i;
			int		n = sizeof(struct rtnl_link_stats)/4;

			a = __RTA_PUT(skb, IFLA_STATS, n*4);
			s = RTA_DATA(a);
			for (i=0; i<n; i++)
				s[i] = stats[i];
		}
	}
	nlh->nlmsg_len = skb->tail - b;
	return skb->len;

nlmsg_failure:
rtattr_failure:
	skb_trim(skb, b - skb->data);
	return -1;
}

Project: linux-2.6

static int neigh_fill_info(struct sk_buff *skb, struct neighbour *n,
			   u32 pid, u32 seq, int event, unsigned int flags)
{
	unsigned long now = jiffies;
	unsigned char *b = skb->tail;
	struct nda_cacheinfo ci;
	int locked = 0;
	u32 probes;
	struct nlmsghdr *nlh = NLMSG_NEW(skb, pid, seq, event,
					 sizeof(struct ndmsg), flags);
	struct ndmsg *ndm = NLMSG_DATA(nlh);

	ndm->ndm_family	 = n->ops->family;
	ndm->ndm_flags	 = n->flags;
	ndm->ndm_type	 = n->type;
	ndm->ndm_ifindex = n->dev->ifindex;
	RTA_PUT(skb, NDA_DST, n->tbl->key_len, n->primary_key);
	read_lock_bh(&n->lock);
	locked		 = 1;
	ndm->ndm_state	 = n->nud_state;
	if (n->nud_state & NUD_VALID)
		RTA_PUT(skb, NDA_LLADDR, n->dev->addr_len, n->ha);
	ci.ndm_used	 = now - n->used;
	ci.ndm_confirmed = now - n->confirmed;
	ci.ndm_updated	 = now - n->updated;
	ci.ndm_refcnt	 = atomic_read(&n->refcnt) - 1;
	probes = atomic_read(&n->probes);
	read_unlock_bh(&n->lock);
	locked		 = 0;
	RTA_PUT(skb, NDA_CACHEINFO, sizeof(ci), &ci);
	RTA_PUT(skb, NDA_PROBES, sizeof(probes), &probes);
	nlh->nlmsg_len	 = skb->tail - b;
	return skb->len;

nlmsg_failure:
rtattr_failure:
	if (locked)
		read_unlock_bh(&n->lock);
	skb_trim(skb, b - skb->data);
	return -1;
}

Project: linux-2.6

static inline int rtnetlink_fill_iwinfo(struct sk_buff *	skb,
					struct net_device *	dev,
					int			type,
					char *			event,
					int			event_len)
{
	struct ifinfomsg *r;
	struct nlmsghdr  *nlh;
	unsigned char	 *b = skb->tail;

	nlh = NLMSG_PUT(skb, 0, 0, type, sizeof(*r));
	r = NLMSG_DATA(nlh);
	r->ifi_family = AF_UNSPEC;
	r->ifi_type = dev->type;
	r->ifi_index = dev->ifindex;
	r->ifi_flags = dev->flags;
	r->ifi_change = 0;	/* Wireless changes don't affect those flags */

	/* Add the wireless events in the netlink packet */
	RTA_PUT(skb, IFLA_WIRELESS,
		event_len, event);

	nlh->nlmsg_len = skb->tail - b;
	return skb->len;

nlmsg_failure:
rtattr_failure:
	skb_trim(skb, b - skb->data);
	return -1;
}

Project: linux-2.6

tcf_fill_node(struct sk_buff *skb, struct tcf_proto *tp, unsigned long fh,
	      u32 pid, u32 seq, u16 flags, int event)
{
	struct tcmsg *tcm;
	struct nlmsghdr  *nlh;
	unsigned char	 *b = skb->tail;

	nlh = NLMSG_NEW(skb, pid, seq, event, sizeof(*tcm), flags);
	tcm = NLMSG_DATA(nlh);
	tcm->tcm_family = AF_UNSPEC;
	tcm->tcm_ifindex = tp->q->dev->ifindex;
	tcm->tcm_parent = tp->classid;
	tcm->tcm_info = TC_H_MAKE(tp->prio, tp->protocol);
	RTA_PUT(skb, TCA_KIND, IFNAMSIZ, tp->ops->kind);
	tcm->tcm_handle = fh;
	if (RTM_DELTFILTER != event) {
		tcm->tcm_handle = 0;
		if (tp->ops->dump && tp->ops->dump(tp, fh, skb, tcm) < 0)
			goto rtattr_failure;
	}
	nlh->nlmsg_len = skb->tail - b;
	return skb->len;

nlmsg_failure:
rtattr_failure:
	skb_trim(skb, b - skb->data);
	return -1;
}

Project: linux-2.6

static int tc_fill_qdisc(struct sk_buff *skb, struct Qdisc *q, u32 clid,
			 u32 pid, u32 seq, u16 flags, int event)
{
	struct tcmsg *tcm;
	struct nlmsghdr  *nlh;
	unsigned char	 *b = skb->tail;
	struct gnet_dump d;

	nlh = NLMSG_NEW(skb, pid, seq, event, sizeof(*tcm), flags);
	tcm = NLMSG_DATA(nlh);
	tcm->tcm_family = AF_UNSPEC;
	tcm->tcm_ifindex = q->dev->ifindex;
	tcm->tcm_parent = clid;
	tcm->tcm_handle = q->handle;
	tcm->tcm_info = atomic_read(&q->refcnt);
	RTA_PUT(skb, TCA_KIND, IFNAMSIZ, q->ops->id);
	if (q->ops->dump && q->ops->dump(q, skb) < 0)
		goto rtattr_failure;
	q->qstats.qlen = q->q.qlen;

	if (gnet_stats_start_copy_compat(skb, TCA_STATS2, TCA_STATS,
			TCA_XSTATS, q->stats_lock, &d) < 0)
		goto rtattr_failure;

	if (q->ops->dump_stats && q->ops->dump_stats(q, &d) < 0)
		goto rtattr_failure;

	if (gnet_stats_copy_basic(&d, &q->bstats) < 0 ||
#ifdef CONFIG_NET_ESTIMATOR
	    gnet_stats_copy_rate_est(&d, &q->rate_est) < 0 ||
#endif
	    gnet_stats_copy_queue(&d, &q->qstats) < 0)
		goto rtattr_failure;
	
	if (gnet_stats_finish_copy(&d) < 0)
		goto rtattr_failure;
	
	nlh->nlmsg_len = skb->tail - b;
	return skb->len;

nlmsg_failure:
rtattr_failure:
	skb_trim(skb, b - skb->data);
	return -1;
}

Project: linux-2.6

static int neightbl_fill_param_info(struct neigh_table *tbl,
				    struct neigh_parms *parms,
				    struct sk_buff *skb,
				    struct netlink_callback *cb)
{
	struct ndtmsg *ndtmsg;
	struct nlmsghdr *nlh;

	nlh = NLMSG_NEW_ANSWER(skb, cb, RTM_NEWNEIGHTBL, sizeof(struct ndtmsg),
			       NLM_F_MULTI);

	ndtmsg = NLMSG_DATA(nlh);

	read_lock_bh(&tbl->lock);
	ndtmsg->ndtm_family = tbl->family;
	RTA_PUT_STRING(skb, NDTA_NAME, tbl->id);

	if (neightbl_fill_parms(skb, parms) < 0)
		goto rtattr_failure;

	read_unlock_bh(&tbl->lock);
	return NLMSG_END(skb, nlh);

rtattr_failure:
	read_unlock_bh(&tbl->lock);
	return NLMSG_CANCEL(skb, nlh);

nlmsg_failure:
	return -1;
}

Project: linux-2.6

static int tca_action_flush(struct rtattr *rta, struct nlmsghdr *n, u32 pid)
{
	struct sk_buff *skb;
	unsigned char *b;
	struct nlmsghdr *nlh;
	struct tcamsg *t;
	struct netlink_callback dcb;
	struct rtattr *x;
	struct rtattr *tb[TCA_ACT_MAX+1];
	struct rtattr *kind;
	struct tc_action *a = create_a(0);
	int err = -EINVAL;

	if (a == NULL) {
		printk("tca_action_flush: couldnt create tc_action\n");
		return err;
	}

	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
	if (!skb) {
		printk("tca_action_flush: failed skb alloc\n");
		kfree(a);
		return -ENOBUFS;
	}

	b = (unsigned char *)skb->tail;

	if (rtattr_parse_nested(tb, TCA_ACT_MAX, rta) < 0)
		goto err_out;

	kind = tb[TCA_ACT_KIND-1];
	a->ops = tc_lookup_action(kind);
	if (a->ops == NULL)
		goto err_out;

	nlh = NLMSG_PUT(skb, pid, n->nlmsg_seq, RTM_DELACTION, sizeof(*t));
	t = NLMSG_DATA(nlh);
	t->tca_family = AF_UNSPEC;

	x = (struct rtattr *) skb->tail;
	RTA_PUT(skb, TCA_ACT_TAB, 0, NULL);

	err = a->ops->walk(skb, &dcb, RTM_DELACTION, a);
	if (err < 0)
		goto rtattr_failure;

	x->rta_len = skb->tail - (u8 *) x;

	nlh->nlmsg_len = skb->tail - b;
	nlh->nlmsg_flags |= NLM_F_ROOT;
	module_put(a->ops->owner);
	kfree(a);
	err = rtnetlink_send(skb, pid, RTMGRP_TC, n->nlmsg_flags&NLM_F_ECHO);
	if (err > 0)
		return 0;

	return err;

rtattr_failure:
	module_put(a->ops->owner);
nlmsg_failure:
err_out:
	kfree_skb(skb);
	kfree(a);
	return err;
}