1508 lines
38 KiB
C
1508 lines
38 KiB
C
/*
|
||
* ebtables
|
||
*
|
||
* Author:
|
||
* Bart De Schuymer <bdschuym@pandora.be>
|
||
*
|
||
* ebtables.c,v 2.0, July, 2002
|
||
*
|
||
* This code is stongly inspired on the iptables code which is
|
||
* Copyright (C) 1999 Paul `Rusty' Russell & Michael J. Neuling
|
||
*
|
||
* This program is free software; you can redistribute it and/or
|
||
* modify it under the terms of the GNU General Public License
|
||
* as published by the Free Software Foundation; either version
|
||
* 2 of the License, or (at your option) any later version.
|
||
*/
|
||
|
||
/* used for print_string */
|
||
#include <linux/sched.h>
|
||
#include <linux/tty.h>
|
||
|
||
#include <linux/kmod.h>
|
||
#include <linux/module.h>
|
||
#include <linux/vmalloc.h>
|
||
#include <linux/netfilter_bridge/ebtables.h>
|
||
#include <linux/spinlock.h>
|
||
#include <asm/uaccess.h>
|
||
#include <linux/smp.h>
|
||
#include <linux/cpumask.h>
|
||
#include <net/sock.h>
|
||
/* needed for logical [in,out]-dev filtering */
|
||
#include "../br_private.h"
|
||
|
||
/* list_named_find */
|
||
#define ASSERT_READ_LOCK(x)
|
||
#define ASSERT_WRITE_LOCK(x)
|
||
#include <linux/netfilter_ipv4/listhelp.h>
|
||
|
||
#if 0
|
||
/* use this for remote debugging
|
||
* Copyright (C) 1998 by Ori Pomerantz
|
||
* Print the string to the appropriate tty, the one
|
||
* the current task uses
|
||
*/
|
||
static void print_string(char *str)
|
||
{
|
||
struct tty_struct *my_tty;
|
||
|
||
/* The tty for the current task */
|
||
my_tty = current->signal->tty;
|
||
if (my_tty != NULL) {
|
||
my_tty->driver->write(my_tty, 0, str, strlen(str));
|
||
my_tty->driver->write(my_tty, 0, "\015\012", 2);
|
||
}
|
||
}
|
||
|
||
#define BUGPRINT(args) print_string(args);
|
||
#else
|
||
#define BUGPRINT(format, args...) printk("kernel msg: ebtables bug: please "\
|
||
"report to author: "format, ## args)
|
||
/* #define BUGPRINT(format, args...) */
|
||
#endif
|
||
#define MEMPRINT(format, args...) printk("kernel msg: ebtables "\
|
||
": out of memory: "format, ## args)
|
||
/* #define MEMPRINT(format, args...) */
|
||
|
||
|
||
|
||
/*
|
||
* Each cpu has its own set of counters, so there is no need for write_lock in
|
||
* the softirq
|
||
* For reading or updating the counters, the user context needs to
|
||
* get a write_lock
|
||
*/
|
||
|
||
/* The size of each set of counters is altered to get cache alignment */
|
||
#define SMP_ALIGN(x) (((x) + SMP_CACHE_BYTES-1) & ~(SMP_CACHE_BYTES-1))
|
||
#define COUNTER_OFFSET(n) (SMP_ALIGN(n * sizeof(struct ebt_counter)))
|
||
#define COUNTER_BASE(c, n, cpu) ((struct ebt_counter *)(((char *)c) + \
|
||
COUNTER_OFFSET(n) * cpu))
|
||
|
||
|
||
|
||
static DECLARE_MUTEX(ebt_mutex);
|
||
static LIST_HEAD(ebt_tables);
|
||
static LIST_HEAD(ebt_targets);
|
||
static LIST_HEAD(ebt_matches);
|
||
static LIST_HEAD(ebt_watchers);
|
||
|
||
static struct ebt_target ebt_standard_target =
|
||
{ {NULL, NULL}, EBT_STANDARD_TARGET, NULL, NULL, NULL, NULL};
|
||
|
||
static inline int ebt_do_watcher (struct ebt_entry_watcher *w,
|
||
const struct sk_buff *skb, unsigned int hooknr, const struct net_device *in,
|
||
const struct net_device *out)
|
||
{
|
||
w->u.watcher->watcher(skb, hooknr, in, out, w->data,
|
||
w->watcher_size);
|
||
/* watchers don't give a verdict */
|
||
return 0;
|
||
}
|
||
|
||
static inline int ebt_do_match (struct ebt_entry_match *m,
|
||
const struct sk_buff *skb, const struct net_device *in,
|
||
const struct net_device *out)
|
||
{
|
||
return m->u.match->match(skb, in, out, m->data,
|
||
m->match_size);
|
||
}
|
||
|
||
static inline int ebt_dev_check(char *entry, const struct net_device *device)
|
||
{
|
||
int i = 0;
|
||
char *devname = device->name;
|
||
|
||
if (*entry == '\0')
|
||
return 0;
|
||
if (!device)
|
||
return 1;
|
||
/* 1 is the wildcard token */
|
||
while (entry[i] != '\0' && entry[i] != 1 && entry[i] == devname[i])
|
||
i++;
|
||
return (devname[i] != entry[i] && entry[i] != 1);
|
||
}
|
||
|
||
#define FWINV2(bool,invflg) ((bool) ^ !!(e->invflags & invflg))
|
||
/* process standard matches */
|
||
static inline int ebt_basic_match(struct ebt_entry *e, struct ethhdr *h,
|
||
const struct net_device *in, const struct net_device *out)
|
||
{
|
||
int verdict, i;
|
||
|
||
if (e->bitmask & EBT_802_3) {
|
||
if (FWINV2(ntohs(h->h_proto) >= 1536, EBT_IPROTO))
|
||
return 1;
|
||
} else if (!(e->bitmask & EBT_NOPROTO) &&
|
||
FWINV2(e->ethproto != h->h_proto, EBT_IPROTO))
|
||
return 1;
|
||
|
||
if (FWINV2(ebt_dev_check(e->in, in), EBT_IIN))
|
||
return 1;
|
||
if (FWINV2(ebt_dev_check(e->out, out), EBT_IOUT))
|
||
return 1;
|
||
if ((!in || !in->br_port) ? 0 : FWINV2(ebt_dev_check(
|
||
e->logical_in, in->br_port->br->dev), EBT_ILOGICALIN))
|
||
return 1;
|
||
if ((!out || !out->br_port) ? 0 : FWINV2(ebt_dev_check(
|
||
e->logical_out, out->br_port->br->dev), EBT_ILOGICALOUT))
|
||
return 1;
|
||
|
||
if (e->bitmask & EBT_SOURCEMAC) {
|
||
verdict = 0;
|
||
for (i = 0; i < 6; i++)
|
||
verdict |= (h->h_source[i] ^ e->sourcemac[i]) &
|
||
e->sourcemsk[i];
|
||
if (FWINV2(verdict != 0, EBT_ISOURCE) )
|
||
return 1;
|
||
}
|
||
if (e->bitmask & EBT_DESTMAC) {
|
||
verdict = 0;
|
||
for (i = 0; i < 6; i++)
|
||
verdict |= (h->h_dest[i] ^ e->destmac[i]) &
|
||
e->destmsk[i];
|
||
if (FWINV2(verdict != 0, EBT_IDEST) )
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Do some firewalling */
|
||
unsigned int ebt_do_table (unsigned int hook, struct sk_buff **pskb,
|
||
const struct net_device *in, const struct net_device *out,
|
||
struct ebt_table *table)
|
||
{
|
||
int i, nentries;
|
||
struct ebt_entry *point;
|
||
struct ebt_counter *counter_base, *cb_base;
|
||
struct ebt_entry_target *t;
|
||
int verdict, sp = 0;
|
||
struct ebt_chainstack *cs;
|
||
struct ebt_entries *chaininfo;
|
||
char *base;
|
||
struct ebt_table_info *private;
|
||
|
||
read_lock_bh(&table->lock);
|
||
private = table->private;
|
||
cb_base = COUNTER_BASE(private->counters, private->nentries,
|
||
smp_processor_id());
|
||
if (private->chainstack)
|
||
cs = private->chainstack[smp_processor_id()];
|
||
else
|
||
cs = NULL;
|
||
chaininfo = private->hook_entry[hook];
|
||
nentries = private->hook_entry[hook]->nentries;
|
||
point = (struct ebt_entry *)(private->hook_entry[hook]->data);
|
||
counter_base = cb_base + private->hook_entry[hook]->counter_offset;
|
||
/* base for chain jumps */
|
||
base = private->entries;
|
||
i = 0;
|
||
while (i < nentries) {
|
||
if (ebt_basic_match(point, eth_hdr(*pskb), in, out))
|
||
goto letscontinue;
|
||
|
||
if (EBT_MATCH_ITERATE(point, ebt_do_match, *pskb, in, out) != 0)
|
||
goto letscontinue;
|
||
|
||
/* increase counter */
|
||
(*(counter_base + i)).pcnt++;
|
||
(*(counter_base + i)).bcnt+=(**pskb).len;
|
||
|
||
/* these should only watch: not modify, nor tell us
|
||
what to do with the packet */
|
||
EBT_WATCHER_ITERATE(point, ebt_do_watcher, *pskb, hook, in,
|
||
out);
|
||
|
||
t = (struct ebt_entry_target *)
|
||
(((char *)point) + point->target_offset);
|
||
/* standard target */
|
||
if (!t->u.target->target)
|
||
verdict = ((struct ebt_standard_target *)t)->verdict;
|
||
else
|
||
verdict = t->u.target->target(pskb, hook,
|
||
in, out, t->data, t->target_size);
|
||
if (verdict == EBT_ACCEPT) {
|
||
read_unlock_bh(&table->lock);
|
||
return NF_ACCEPT;
|
||
}
|
||
if (verdict == EBT_DROP) {
|
||
read_unlock_bh(&table->lock);
|
||
return NF_DROP;
|
||
}
|
||
if (verdict == EBT_RETURN) {
|
||
letsreturn:
|
||
#ifdef CONFIG_NETFILTER_DEBUG
|
||
if (sp == 0) {
|
||
BUGPRINT("RETURN on base chain");
|
||
/* act like this is EBT_CONTINUE */
|
||
goto letscontinue;
|
||
}
|
||
#endif
|
||
sp--;
|
||
/* put all the local variables right */
|
||
i = cs[sp].n;
|
||
chaininfo = cs[sp].chaininfo;
|
||
nentries = chaininfo->nentries;
|
||
point = cs[sp].e;
|
||
counter_base = cb_base +
|
||
chaininfo->counter_offset;
|
||
continue;
|
||
}
|
||
if (verdict == EBT_CONTINUE)
|
||
goto letscontinue;
|
||
#ifdef CONFIG_NETFILTER_DEBUG
|
||
if (verdict < 0) {
|
||
BUGPRINT("bogus standard verdict\n");
|
||
read_unlock_bh(&table->lock);
|
||
return NF_DROP;
|
||
}
|
||
#endif
|
||
/* jump to a udc */
|
||
cs[sp].n = i + 1;
|
||
cs[sp].chaininfo = chaininfo;
|
||
cs[sp].e = (struct ebt_entry *)
|
||
(((char *)point) + point->next_offset);
|
||
i = 0;
|
||
chaininfo = (struct ebt_entries *) (base + verdict);
|
||
#ifdef CONFIG_NETFILTER_DEBUG
|
||
if (chaininfo->distinguisher) {
|
||
BUGPRINT("jump to non-chain\n");
|
||
read_unlock_bh(&table->lock);
|
||
return NF_DROP;
|
||
}
|
||
#endif
|
||
nentries = chaininfo->nentries;
|
||
point = (struct ebt_entry *)chaininfo->data;
|
||
counter_base = cb_base + chaininfo->counter_offset;
|
||
sp++;
|
||
continue;
|
||
letscontinue:
|
||
point = (struct ebt_entry *)
|
||
(((char *)point) + point->next_offset);
|
||
i++;
|
||
}
|
||
|
||
/* I actually like this :) */
|
||
if (chaininfo->policy == EBT_RETURN)
|
||
goto letsreturn;
|
||
if (chaininfo->policy == EBT_ACCEPT) {
|
||
read_unlock_bh(&table->lock);
|
||
return NF_ACCEPT;
|
||
}
|
||
read_unlock_bh(&table->lock);
|
||
return NF_DROP;
|
||
}
|
||
|
||
/* If it succeeds, returns element and locks mutex */
|
||
static inline void *
|
||
find_inlist_lock_noload(struct list_head *head, const char *name, int *error,
|
||
struct semaphore *mutex)
|
||
{
|
||
void *ret;
|
||
|
||
*error = down_interruptible(mutex);
|
||
if (*error != 0)
|
||
return NULL;
|
||
|
||
ret = list_named_find(head, name);
|
||
if (!ret) {
|
||
*error = -ENOENT;
|
||
up(mutex);
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
#ifndef CONFIG_KMOD
|
||
#define find_inlist_lock(h,n,p,e,m) find_inlist_lock_noload((h),(n),(e),(m))
|
||
#else
|
||
static void *
|
||
find_inlist_lock(struct list_head *head, const char *name, const char *prefix,
|
||
int *error, struct semaphore *mutex)
|
||
{
|
||
void *ret;
|
||
|
||
ret = find_inlist_lock_noload(head, name, error, mutex);
|
||
if (!ret) {
|
||
request_module("%s%s", prefix, name);
|
||
ret = find_inlist_lock_noload(head, name, error, mutex);
|
||
}
|
||
return ret;
|
||
}
|
||
#endif
|
||
|
||
static inline struct ebt_table *
|
||
find_table_lock(const char *name, int *error, struct semaphore *mutex)
|
||
{
|
||
return find_inlist_lock(&ebt_tables, name, "ebtable_", error, mutex);
|
||
}
|
||
|
||
static inline struct ebt_match *
|
||
find_match_lock(const char *name, int *error, struct semaphore *mutex)
|
||
{
|
||
return find_inlist_lock(&ebt_matches, name, "ebt_", error, mutex);
|
||
}
|
||
|
||
static inline struct ebt_watcher *
|
||
find_watcher_lock(const char *name, int *error, struct semaphore *mutex)
|
||
{
|
||
return find_inlist_lock(&ebt_watchers, name, "ebt_", error, mutex);
|
||
}
|
||
|
||
static inline struct ebt_target *
|
||
find_target_lock(const char *name, int *error, struct semaphore *mutex)
|
||
{
|
||
return find_inlist_lock(&ebt_targets, name, "ebt_", error, mutex);
|
||
}
|
||
|
||
static inline int
|
||
ebt_check_match(struct ebt_entry_match *m, struct ebt_entry *e,
|
||
const char *name, unsigned int hookmask, unsigned int *cnt)
|
||
{
|
||
struct ebt_match *match;
|
||
int ret;
|
||
|
||
if (((char *)m) + m->match_size + sizeof(struct ebt_entry_match) >
|
||
((char *)e) + e->watchers_offset)
|
||
return -EINVAL;
|
||
match = find_match_lock(m->u.name, &ret, &ebt_mutex);
|
||
if (!match)
|
||
return ret;
|
||
m->u.match = match;
|
||
if (!try_module_get(match->me)) {
|
||
up(&ebt_mutex);
|
||
return -ENOENT;
|
||
}
|
||
up(&ebt_mutex);
|
||
if (match->check &&
|
||
match->check(name, hookmask, e, m->data, m->match_size) != 0) {
|
||
BUGPRINT("match->check failed\n");
|
||
module_put(match->me);
|
||
return -EINVAL;
|
||
}
|
||
(*cnt)++;
|
||
return 0;
|
||
}
|
||
|
||
static inline int
|
||
ebt_check_watcher(struct ebt_entry_watcher *w, struct ebt_entry *e,
|
||
const char *name, unsigned int hookmask, unsigned int *cnt)
|
||
{
|
||
struct ebt_watcher *watcher;
|
||
int ret;
|
||
|
||
if (((char *)w) + w->watcher_size + sizeof(struct ebt_entry_watcher) >
|
||
((char *)e) + e->target_offset)
|
||
return -EINVAL;
|
||
watcher = find_watcher_lock(w->u.name, &ret, &ebt_mutex);
|
||
if (!watcher)
|
||
return ret;
|
||
w->u.watcher = watcher;
|
||
if (!try_module_get(watcher->me)) {
|
||
up(&ebt_mutex);
|
||
return -ENOENT;
|
||
}
|
||
up(&ebt_mutex);
|
||
if (watcher->check &&
|
||
watcher->check(name, hookmask, e, w->data, w->watcher_size) != 0) {
|
||
BUGPRINT("watcher->check failed\n");
|
||
module_put(watcher->me);
|
||
return -EINVAL;
|
||
}
|
||
(*cnt)++;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* this one is very careful, as it is the first function
|
||
* to parse the userspace data
|
||
*/
|
||
static inline int
|
||
ebt_check_entry_size_and_hooks(struct ebt_entry *e,
|
||
struct ebt_table_info *newinfo, char *base, char *limit,
|
||
struct ebt_entries **hook_entries, unsigned int *n, unsigned int *cnt,
|
||
unsigned int *totalcnt, unsigned int *udc_cnt, unsigned int valid_hooks)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < NF_BR_NUMHOOKS; i++) {
|
||
if ((valid_hooks & (1 << i)) == 0)
|
||
continue;
|
||
if ( (char *)hook_entries[i] - base ==
|
||
(char *)e - newinfo->entries)
|
||
break;
|
||
}
|
||
/* beginning of a new chain
|
||
if i == NF_BR_NUMHOOKS it must be a user defined chain */
|
||
if (i != NF_BR_NUMHOOKS || !(e->bitmask & EBT_ENTRY_OR_ENTRIES)) {
|
||
if ((e->bitmask & EBT_ENTRY_OR_ENTRIES) != 0) {
|
||
/* we make userspace set this right,
|
||
so there is no misunderstanding */
|
||
BUGPRINT("EBT_ENTRY_OR_ENTRIES shouldn't be set "
|
||
"in distinguisher\n");
|
||
return -EINVAL;
|
||
}
|
||
/* this checks if the previous chain has as many entries
|
||
as it said it has */
|
||
if (*n != *cnt) {
|
||
BUGPRINT("nentries does not equal the nr of entries "
|
||
"in the chain\n");
|
||
return -EINVAL;
|
||
}
|
||
/* before we look at the struct, be sure it is not too big */
|
||
if ((char *)hook_entries[i] + sizeof(struct ebt_entries)
|
||
> limit) {
|
||
BUGPRINT("entries_size too small\n");
|
||
return -EINVAL;
|
||
}
|
||
if (((struct ebt_entries *)e)->policy != EBT_DROP &&
|
||
((struct ebt_entries *)e)->policy != EBT_ACCEPT) {
|
||
/* only RETURN from udc */
|
||
if (i != NF_BR_NUMHOOKS ||
|
||
((struct ebt_entries *)e)->policy != EBT_RETURN) {
|
||
BUGPRINT("bad policy\n");
|
||
return -EINVAL;
|
||
}
|
||
}
|
||
if (i == NF_BR_NUMHOOKS) /* it's a user defined chain */
|
||
(*udc_cnt)++;
|
||
else
|
||
newinfo->hook_entry[i] = (struct ebt_entries *)e;
|
||
if (((struct ebt_entries *)e)->counter_offset != *totalcnt) {
|
||
BUGPRINT("counter_offset != totalcnt");
|
||
return -EINVAL;
|
||
}
|
||
*n = ((struct ebt_entries *)e)->nentries;
|
||
*cnt = 0;
|
||
return 0;
|
||
}
|
||
/* a plain old entry, heh */
|
||
if (sizeof(struct ebt_entry) > e->watchers_offset ||
|
||
e->watchers_offset > e->target_offset ||
|
||
e->target_offset >= e->next_offset) {
|
||
BUGPRINT("entry offsets not in right order\n");
|
||
return -EINVAL;
|
||
}
|
||
/* this is not checked anywhere else */
|
||
if (e->next_offset - e->target_offset < sizeof(struct ebt_entry_target)) {
|
||
BUGPRINT("target size too small\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
(*cnt)++;
|
||
(*totalcnt)++;
|
||
return 0;
|
||
}
|
||
|
||
struct ebt_cl_stack
|
||
{
|
||
struct ebt_chainstack cs;
|
||
int from;
|
||
unsigned int hookmask;
|
||
};
|
||
|
||
/*
|
||
* we need these positions to check that the jumps to a different part of the
|
||
* entries is a jump to the beginning of a new chain.
|
||
*/
|
||
static inline int
|
||
ebt_get_udc_positions(struct ebt_entry *e, struct ebt_table_info *newinfo,
|
||
struct ebt_entries **hook_entries, unsigned int *n, unsigned int valid_hooks,
|
||
struct ebt_cl_stack *udc)
|
||
{
|
||
int i;
|
||
|
||
/* we're only interested in chain starts */
|
||
if (e->bitmask & EBT_ENTRY_OR_ENTRIES)
|
||
return 0;
|
||
for (i = 0; i < NF_BR_NUMHOOKS; i++) {
|
||
if ((valid_hooks & (1 << i)) == 0)
|
||
continue;
|
||
if (newinfo->hook_entry[i] == (struct ebt_entries *)e)
|
||
break;
|
||
}
|
||
/* only care about udc */
|
||
if (i != NF_BR_NUMHOOKS)
|
||
return 0;
|
||
|
||
udc[*n].cs.chaininfo = (struct ebt_entries *)e;
|
||
/* these initialisations are depended on later in check_chainloops() */
|
||
udc[*n].cs.n = 0;
|
||
udc[*n].hookmask = 0;
|
||
|
||
(*n)++;
|
||
return 0;
|
||
}
|
||
|
||
static inline int
|
||
ebt_cleanup_match(struct ebt_entry_match *m, unsigned int *i)
|
||
{
|
||
if (i && (*i)-- == 0)
|
||
return 1;
|
||
if (m->u.match->destroy)
|
||
m->u.match->destroy(m->data, m->match_size);
|
||
module_put(m->u.match->me);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static inline int
|
||
ebt_cleanup_watcher(struct ebt_entry_watcher *w, unsigned int *i)
|
||
{
|
||
if (i && (*i)-- == 0)
|
||
return 1;
|
||
if (w->u.watcher->destroy)
|
||
w->u.watcher->destroy(w->data, w->watcher_size);
|
||
module_put(w->u.watcher->me);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static inline int
|
||
ebt_cleanup_entry(struct ebt_entry *e, unsigned int *cnt)
|
||
{
|
||
struct ebt_entry_target *t;
|
||
|
||
if ((e->bitmask & EBT_ENTRY_OR_ENTRIES) == 0)
|
||
return 0;
|
||
/* we're done */
|
||
if (cnt && (*cnt)-- == 0)
|
||
return 1;
|
||
EBT_WATCHER_ITERATE(e, ebt_cleanup_watcher, NULL);
|
||
EBT_MATCH_ITERATE(e, ebt_cleanup_match, NULL);
|
||
t = (struct ebt_entry_target *)(((char *)e) + e->target_offset);
|
||
if (t->u.target->destroy)
|
||
t->u.target->destroy(t->data, t->target_size);
|
||
module_put(t->u.target->me);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static inline int
|
||
ebt_check_entry(struct ebt_entry *e, struct ebt_table_info *newinfo,
|
||
const char *name, unsigned int *cnt, unsigned int valid_hooks,
|
||
struct ebt_cl_stack *cl_s, unsigned int udc_cnt)
|
||
{
|
||
struct ebt_entry_target *t;
|
||
struct ebt_target *target;
|
||
unsigned int i, j, hook = 0, hookmask = 0;
|
||
int ret;
|
||
|
||
/* don't mess with the struct ebt_entries */
|
||
if ((e->bitmask & EBT_ENTRY_OR_ENTRIES) == 0)
|
||
return 0;
|
||
|
||
if (e->bitmask & ~EBT_F_MASK) {
|
||
BUGPRINT("Unknown flag for bitmask\n");
|
||
return -EINVAL;
|
||
}
|
||
if (e->invflags & ~EBT_INV_MASK) {
|
||
BUGPRINT("Unknown flag for inv bitmask\n");
|
||
return -EINVAL;
|
||
}
|
||
if ( (e->bitmask & EBT_NOPROTO) && (e->bitmask & EBT_802_3) ) {
|
||
BUGPRINT("NOPROTO & 802_3 not allowed\n");
|
||
return -EINVAL;
|
||
}
|
||
/* what hook do we belong to? */
|
||
for (i = 0; i < NF_BR_NUMHOOKS; i++) {
|
||
if ((valid_hooks & (1 << i)) == 0)
|
||
continue;
|
||
if ((char *)newinfo->hook_entry[i] < (char *)e)
|
||
hook = i;
|
||
else
|
||
break;
|
||
}
|
||
/* (1 << NF_BR_NUMHOOKS) tells the check functions the rule is on
|
||
a base chain */
|
||
if (i < NF_BR_NUMHOOKS)
|
||
hookmask = (1 << hook) | (1 << NF_BR_NUMHOOKS);
|
||
else {
|
||
for (i = 0; i < udc_cnt; i++)
|
||
if ((char *)(cl_s[i].cs.chaininfo) > (char *)e)
|
||
break;
|
||
if (i == 0)
|
||
hookmask = (1 << hook) | (1 << NF_BR_NUMHOOKS);
|
||
else
|
||
hookmask = cl_s[i - 1].hookmask;
|
||
}
|
||
i = 0;
|
||
ret = EBT_MATCH_ITERATE(e, ebt_check_match, e, name, hookmask, &i);
|
||
if (ret != 0)
|
||
goto cleanup_matches;
|
||
j = 0;
|
||
ret = EBT_WATCHER_ITERATE(e, ebt_check_watcher, e, name, hookmask, &j);
|
||
if (ret != 0)
|
||
goto cleanup_watchers;
|
||
t = (struct ebt_entry_target *)(((char *)e) + e->target_offset);
|
||
target = find_target_lock(t->u.name, &ret, &ebt_mutex);
|
||
if (!target)
|
||
goto cleanup_watchers;
|
||
if (!try_module_get(target->me)) {
|
||
up(&ebt_mutex);
|
||
ret = -ENOENT;
|
||
goto cleanup_watchers;
|
||
}
|
||
up(&ebt_mutex);
|
||
|
||
t->u.target = target;
|
||
if (t->u.target == &ebt_standard_target) {
|
||
if (e->target_offset + sizeof(struct ebt_standard_target) >
|
||
e->next_offset) {
|
||
BUGPRINT("Standard target size too big\n");
|
||
ret = -EFAULT;
|
||
goto cleanup_watchers;
|
||
}
|
||
if (((struct ebt_standard_target *)t)->verdict <
|
||
-NUM_STANDARD_TARGETS) {
|
||
BUGPRINT("Invalid standard target\n");
|
||
ret = -EFAULT;
|
||
goto cleanup_watchers;
|
||
}
|
||
} else if ((e->target_offset + t->target_size +
|
||
sizeof(struct ebt_entry_target) > e->next_offset) ||
|
||
(t->u.target->check &&
|
||
t->u.target->check(name, hookmask, e, t->data, t->target_size) != 0)){
|
||
module_put(t->u.target->me);
|
||
ret = -EFAULT;
|
||
goto cleanup_watchers;
|
||
}
|
||
(*cnt)++;
|
||
return 0;
|
||
cleanup_watchers:
|
||
EBT_WATCHER_ITERATE(e, ebt_cleanup_watcher, &j);
|
||
cleanup_matches:
|
||
EBT_MATCH_ITERATE(e, ebt_cleanup_match, &i);
|
||
return ret;
|
||
}
|
||
|
||
/*
|
||
* checks for loops and sets the hook mask for udc
|
||
* the hook mask for udc tells us from which base chains the udc can be
|
||
* accessed. This mask is a parameter to the check() functions of the extensions
|
||
*/
|
||
static int check_chainloops(struct ebt_entries *chain, struct ebt_cl_stack *cl_s,
|
||
unsigned int udc_cnt, unsigned int hooknr, char *base)
|
||
{
|
||
int i, chain_nr = -1, pos = 0, nentries = chain->nentries, verdict;
|
||
struct ebt_entry *e = (struct ebt_entry *)chain->data;
|
||
struct ebt_entry_target *t;
|
||
|
||
while (pos < nentries || chain_nr != -1) {
|
||
/* end of udc, go back one 'recursion' step */
|
||
if (pos == nentries) {
|
||
/* put back values of the time when this chain was called */
|
||
e = cl_s[chain_nr].cs.e;
|
||
if (cl_s[chain_nr].from != -1)
|
||
nentries =
|
||
cl_s[cl_s[chain_nr].from].cs.chaininfo->nentries;
|
||
else
|
||
nentries = chain->nentries;
|
||
pos = cl_s[chain_nr].cs.n;
|
||
/* make sure we won't see a loop that isn't one */
|
||
cl_s[chain_nr].cs.n = 0;
|
||
chain_nr = cl_s[chain_nr].from;
|
||
if (pos == nentries)
|
||
continue;
|
||
}
|
||
t = (struct ebt_entry_target *)
|
||
(((char *)e) + e->target_offset);
|
||
if (strcmp(t->u.name, EBT_STANDARD_TARGET))
|
||
goto letscontinue;
|
||
if (e->target_offset + sizeof(struct ebt_standard_target) >
|
||
e->next_offset) {
|
||
BUGPRINT("Standard target size too big\n");
|
||
return -1;
|
||
}
|
||
verdict = ((struct ebt_standard_target *)t)->verdict;
|
||
if (verdict >= 0) { /* jump to another chain */
|
||
struct ebt_entries *hlp2 =
|
||
(struct ebt_entries *)(base + verdict);
|
||
for (i = 0; i < udc_cnt; i++)
|
||
if (hlp2 == cl_s[i].cs.chaininfo)
|
||
break;
|
||
/* bad destination or loop */
|
||
if (i == udc_cnt) {
|
||
BUGPRINT("bad destination\n");
|
||
return -1;
|
||
}
|
||
if (cl_s[i].cs.n) {
|
||
BUGPRINT("loop\n");
|
||
return -1;
|
||
}
|
||
/* this can't be 0, so the above test is correct */
|
||
cl_s[i].cs.n = pos + 1;
|
||
pos = 0;
|
||
cl_s[i].cs.e = ((void *)e + e->next_offset);
|
||
e = (struct ebt_entry *)(hlp2->data);
|
||
nentries = hlp2->nentries;
|
||
cl_s[i].from = chain_nr;
|
||
chain_nr = i;
|
||
/* this udc is accessible from the base chain for hooknr */
|
||
cl_s[i].hookmask |= (1 << hooknr);
|
||
continue;
|
||
}
|
||
letscontinue:
|
||
e = (void *)e + e->next_offset;
|
||
pos++;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* do the parsing of the table/chains/entries/matches/watchers/targets, heh */
|
||
static int translate_table(struct ebt_replace *repl,
|
||
struct ebt_table_info *newinfo)
|
||
{
|
||
unsigned int i, j, k, udc_cnt;
|
||
int ret;
|
||
struct ebt_cl_stack *cl_s = NULL; /* used in the checking for chain loops */
|
||
|
||
i = 0;
|
||
while (i < NF_BR_NUMHOOKS && !(repl->valid_hooks & (1 << i)))
|
||
i++;
|
||
if (i == NF_BR_NUMHOOKS) {
|
||
BUGPRINT("No valid hooks specified\n");
|
||
return -EINVAL;
|
||
}
|
||
if (repl->hook_entry[i] != (struct ebt_entries *)repl->entries) {
|
||
BUGPRINT("Chains don't start at beginning\n");
|
||
return -EINVAL;
|
||
}
|
||
/* make sure chains are ordered after each other in same order
|
||
as their corresponding hooks */
|
||
for (j = i + 1; j < NF_BR_NUMHOOKS; j++) {
|
||
if (!(repl->valid_hooks & (1 << j)))
|
||
continue;
|
||
if ( repl->hook_entry[j] <= repl->hook_entry[i] ) {
|
||
BUGPRINT("Hook order must be followed\n");
|
||
return -EINVAL;
|
||
}
|
||
i = j;
|
||
}
|
||
|
||
for (i = 0; i < NF_BR_NUMHOOKS; i++)
|
||
newinfo->hook_entry[i] = NULL;
|
||
|
||
newinfo->entries_size = repl->entries_size;
|
||
newinfo->nentries = repl->nentries;
|
||
|
||
/* do some early checkings and initialize some things */
|
||
i = 0; /* holds the expected nr. of entries for the chain */
|
||
j = 0; /* holds the up to now counted entries for the chain */
|
||
k = 0; /* holds the total nr. of entries, should equal
|
||
newinfo->nentries afterwards */
|
||
udc_cnt = 0; /* will hold the nr. of user defined chains (udc) */
|
||
ret = EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
|
||
ebt_check_entry_size_and_hooks, newinfo, repl->entries,
|
||
repl->entries + repl->entries_size, repl->hook_entry, &i, &j, &k,
|
||
&udc_cnt, repl->valid_hooks);
|
||
|
||
if (ret != 0)
|
||
return ret;
|
||
|
||
if (i != j) {
|
||
BUGPRINT("nentries does not equal the nr of entries in the "
|
||
"(last) chain\n");
|
||
return -EINVAL;
|
||
}
|
||
if (k != newinfo->nentries) {
|
||
BUGPRINT("Total nentries is wrong\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
/* check if all valid hooks have a chain */
|
||
for (i = 0; i < NF_BR_NUMHOOKS; i++) {
|
||
if (newinfo->hook_entry[i] == NULL &&
|
||
(repl->valid_hooks & (1 << i))) {
|
||
BUGPRINT("Valid hook without chain\n");
|
||
return -EINVAL;
|
||
}
|
||
}
|
||
|
||
/* get the location of the udc, put them in an array
|
||
while we're at it, allocate the chainstack */
|
||
if (udc_cnt) {
|
||
/* this will get free'd in do_replace()/ebt_register_table()
|
||
if an error occurs */
|
||
newinfo->chainstack = (struct ebt_chainstack **)
|
||
vmalloc((highest_possible_processor_id()+1)
|
||
* sizeof(struct ebt_chainstack));
|
||
if (!newinfo->chainstack)
|
||
return -ENOMEM;
|
||
for_each_cpu(i) {
|
||
newinfo->chainstack[i] =
|
||
vmalloc(udc_cnt * sizeof(struct ebt_chainstack));
|
||
if (!newinfo->chainstack[i]) {
|
||
while (i)
|
||
vfree(newinfo->chainstack[--i]);
|
||
vfree(newinfo->chainstack);
|
||
newinfo->chainstack = NULL;
|
||
return -ENOMEM;
|
||
}
|
||
}
|
||
|
||
cl_s = (struct ebt_cl_stack *)
|
||
vmalloc(udc_cnt * sizeof(struct ebt_cl_stack));
|
||
if (!cl_s)
|
||
return -ENOMEM;
|
||
i = 0; /* the i'th udc */
|
||
EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
|
||
ebt_get_udc_positions, newinfo, repl->hook_entry, &i,
|
||
repl->valid_hooks, cl_s);
|
||
/* sanity check */
|
||
if (i != udc_cnt) {
|
||
BUGPRINT("i != udc_cnt\n");
|
||
vfree(cl_s);
|
||
return -EFAULT;
|
||
}
|
||
}
|
||
|
||
/* Check for loops */
|
||
for (i = 0; i < NF_BR_NUMHOOKS; i++)
|
||
if (repl->valid_hooks & (1 << i))
|
||
if (check_chainloops(newinfo->hook_entry[i],
|
||
cl_s, udc_cnt, i, newinfo->entries)) {
|
||
vfree(cl_s);
|
||
return -EINVAL;
|
||
}
|
||
|
||
/* we now know the following (along with E=mc<6D>):
|
||
- the nr of entries in each chain is right
|
||
- the size of the allocated space is right
|
||
- all valid hooks have a corresponding chain
|
||
- there are no loops
|
||
- wrong data can still be on the level of a single entry
|
||
- could be there are jumps to places that are not the
|
||
beginning of a chain. This can only occur in chains that
|
||
are not accessible from any base chains, so we don't care. */
|
||
|
||
/* used to know what we need to clean up if something goes wrong */
|
||
i = 0;
|
||
ret = EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
|
||
ebt_check_entry, newinfo, repl->name, &i, repl->valid_hooks,
|
||
cl_s, udc_cnt);
|
||
if (ret != 0) {
|
||
EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
|
||
ebt_cleanup_entry, &i);
|
||
}
|
||
vfree(cl_s);
|
||
return ret;
|
||
}
|
||
|
||
/* called under write_lock */
|
||
static void get_counters(struct ebt_counter *oldcounters,
|
||
struct ebt_counter *counters, unsigned int nentries)
|
||
{
|
||
int i, cpu;
|
||
struct ebt_counter *counter_base;
|
||
|
||
/* counters of cpu 0 */
|
||
memcpy(counters, oldcounters,
|
||
sizeof(struct ebt_counter) * nentries);
|
||
|
||
/* add other counters to those of cpu 0 */
|
||
for_each_cpu(cpu) {
|
||
if (cpu == 0)
|
||
continue;
|
||
counter_base = COUNTER_BASE(oldcounters, nentries, cpu);
|
||
for (i = 0; i < nentries; i++) {
|
||
counters[i].pcnt += counter_base[i].pcnt;
|
||
counters[i].bcnt += counter_base[i].bcnt;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* replace the table */
|
||
static int do_replace(void __user *user, unsigned int len)
|
||
{
|
||
int ret, i, countersize;
|
||
struct ebt_table_info *newinfo;
|
||
struct ebt_replace tmp;
|
||
struct ebt_table *t;
|
||
struct ebt_counter *counterstmp = NULL;
|
||
/* used to be able to unlock earlier */
|
||
struct ebt_table_info *table;
|
||
|
||
if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
|
||
return -EFAULT;
|
||
|
||
if (len != sizeof(tmp) + tmp.entries_size) {
|
||
BUGPRINT("Wrong len argument\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
if (tmp.entries_size == 0) {
|
||
BUGPRINT("Entries_size never zero\n");
|
||
return -EINVAL;
|
||
}
|
||
countersize = COUNTER_OFFSET(tmp.nentries) *
|
||
(highest_possible_processor_id()+1);
|
||
newinfo = (struct ebt_table_info *)
|
||
vmalloc(sizeof(struct ebt_table_info) + countersize);
|
||
if (!newinfo)
|
||
return -ENOMEM;
|
||
|
||
if (countersize)
|
||
memset(newinfo->counters, 0, countersize);
|
||
|
||
newinfo->entries = vmalloc(tmp.entries_size);
|
||
if (!newinfo->entries) {
|
||
ret = -ENOMEM;
|
||
goto free_newinfo;
|
||
}
|
||
if (copy_from_user(
|
||
newinfo->entries, tmp.entries, tmp.entries_size) != 0) {
|
||
BUGPRINT("Couldn't copy entries from userspace\n");
|
||
ret = -EFAULT;
|
||
goto free_entries;
|
||
}
|
||
|
||
/* the user wants counters back
|
||
the check on the size is done later, when we have the lock */
|
||
if (tmp.num_counters) {
|
||
counterstmp = (struct ebt_counter *)
|
||
vmalloc(tmp.num_counters * sizeof(struct ebt_counter));
|
||
if (!counterstmp) {
|
||
ret = -ENOMEM;
|
||
goto free_entries;
|
||
}
|
||
}
|
||
else
|
||
counterstmp = NULL;
|
||
|
||
/* this can get initialized by translate_table() */
|
||
newinfo->chainstack = NULL;
|
||
ret = translate_table(&tmp, newinfo);
|
||
|
||
if (ret != 0)
|
||
goto free_counterstmp;
|
||
|
||
t = find_table_lock(tmp.name, &ret, &ebt_mutex);
|
||
if (!t) {
|
||
ret = -ENOENT;
|
||
goto free_iterate;
|
||
}
|
||
|
||
/* the table doesn't like it */
|
||
if (t->check && (ret = t->check(newinfo, tmp.valid_hooks)))
|
||
goto free_unlock;
|
||
|
||
if (tmp.num_counters && tmp.num_counters != t->private->nentries) {
|
||
BUGPRINT("Wrong nr. of counters requested\n");
|
||
ret = -EINVAL;
|
||
goto free_unlock;
|
||
}
|
||
|
||
/* we have the mutex lock, so no danger in reading this pointer */
|
||
table = t->private;
|
||
/* make sure the table can only be rmmod'ed if it contains no rules */
|
||
if (!table->nentries && newinfo->nentries && !try_module_get(t->me)) {
|
||
ret = -ENOENT;
|
||
goto free_unlock;
|
||
} else if (table->nentries && !newinfo->nentries)
|
||
module_put(t->me);
|
||
/* we need an atomic snapshot of the counters */
|
||
write_lock_bh(&t->lock);
|
||
if (tmp.num_counters)
|
||
get_counters(t->private->counters, counterstmp,
|
||
t->private->nentries);
|
||
|
||
t->private = newinfo;
|
||
write_unlock_bh(&t->lock);
|
||
up(&ebt_mutex);
|
||
/* so, a user can change the chains while having messed up her counter
|
||
allocation. Only reason why this is done is because this way the lock
|
||
is held only once, while this doesn't bring the kernel into a
|
||
dangerous state. */
|
||
if (tmp.num_counters &&
|
||
copy_to_user(tmp.counters, counterstmp,
|
||
tmp.num_counters * sizeof(struct ebt_counter))) {
|
||
BUGPRINT("Couldn't copy counters to userspace\n");
|
||
ret = -EFAULT;
|
||
}
|
||
else
|
||
ret = 0;
|
||
|
||
/* decrease module count and free resources */
|
||
EBT_ENTRY_ITERATE(table->entries, table->entries_size,
|
||
ebt_cleanup_entry, NULL);
|
||
|
||
vfree(table->entries);
|
||
if (table->chainstack) {
|
||
for_each_cpu(i)
|
||
vfree(table->chainstack[i]);
|
||
vfree(table->chainstack);
|
||
}
|
||
vfree(table);
|
||
|
||
vfree(counterstmp);
|
||
return ret;
|
||
|
||
free_unlock:
|
||
up(&ebt_mutex);
|
||
free_iterate:
|
||
EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
|
||
ebt_cleanup_entry, NULL);
|
||
free_counterstmp:
|
||
vfree(counterstmp);
|
||
/* can be initialized in translate_table() */
|
||
if (newinfo->chainstack) {
|
||
for_each_cpu(i)
|
||
vfree(newinfo->chainstack[i]);
|
||
vfree(newinfo->chainstack);
|
||
}
|
||
free_entries:
|
||
vfree(newinfo->entries);
|
||
free_newinfo:
|
||
vfree(newinfo);
|
||
return ret;
|
||
}
|
||
|
||
int ebt_register_target(struct ebt_target *target)
|
||
{
|
||
int ret;
|
||
|
||
ret = down_interruptible(&ebt_mutex);
|
||
if (ret != 0)
|
||
return ret;
|
||
if (!list_named_insert(&ebt_targets, target)) {
|
||
up(&ebt_mutex);
|
||
return -EEXIST;
|
||
}
|
||
up(&ebt_mutex);
|
||
|
||
return 0;
|
||
}
|
||
|
||
void ebt_unregister_target(struct ebt_target *target)
|
||
{
|
||
down(&ebt_mutex);
|
||
LIST_DELETE(&ebt_targets, target);
|
||
up(&ebt_mutex);
|
||
}
|
||
|
||
int ebt_register_match(struct ebt_match *match)
|
||
{
|
||
int ret;
|
||
|
||
ret = down_interruptible(&ebt_mutex);
|
||
if (ret != 0)
|
||
return ret;
|
||
if (!list_named_insert(&ebt_matches, match)) {
|
||
up(&ebt_mutex);
|
||
return -EEXIST;
|
||
}
|
||
up(&ebt_mutex);
|
||
|
||
return 0;
|
||
}
|
||
|
||
void ebt_unregister_match(struct ebt_match *match)
|
||
{
|
||
down(&ebt_mutex);
|
||
LIST_DELETE(&ebt_matches, match);
|
||
up(&ebt_mutex);
|
||
}
|
||
|
||
int ebt_register_watcher(struct ebt_watcher *watcher)
|
||
{
|
||
int ret;
|
||
|
||
ret = down_interruptible(&ebt_mutex);
|
||
if (ret != 0)
|
||
return ret;
|
||
if (!list_named_insert(&ebt_watchers, watcher)) {
|
||
up(&ebt_mutex);
|
||
return -EEXIST;
|
||
}
|
||
up(&ebt_mutex);
|
||
|
||
return 0;
|
||
}
|
||
|
||
void ebt_unregister_watcher(struct ebt_watcher *watcher)
|
||
{
|
||
down(&ebt_mutex);
|
||
LIST_DELETE(&ebt_watchers, watcher);
|
||
up(&ebt_mutex);
|
||
}
|
||
|
||
int ebt_register_table(struct ebt_table *table)
|
||
{
|
||
struct ebt_table_info *newinfo;
|
||
int ret, i, countersize;
|
||
|
||
if (!table || !table->table ||!table->table->entries ||
|
||
table->table->entries_size == 0 ||
|
||
table->table->counters || table->private) {
|
||
BUGPRINT("Bad table data for ebt_register_table!!!\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
countersize = COUNTER_OFFSET(table->table->nentries) *
|
||
(highest_possible_processor_id()+1);
|
||
newinfo = (struct ebt_table_info *)
|
||
vmalloc(sizeof(struct ebt_table_info) + countersize);
|
||
ret = -ENOMEM;
|
||
if (!newinfo)
|
||
return -ENOMEM;
|
||
|
||
newinfo->entries = vmalloc(table->table->entries_size);
|
||
if (!(newinfo->entries))
|
||
goto free_newinfo;
|
||
|
||
memcpy(newinfo->entries, table->table->entries,
|
||
table->table->entries_size);
|
||
|
||
if (countersize)
|
||
memset(newinfo->counters, 0, countersize);
|
||
|
||
/* fill in newinfo and parse the entries */
|
||
newinfo->chainstack = NULL;
|
||
ret = translate_table(table->table, newinfo);
|
||
if (ret != 0) {
|
||
BUGPRINT("Translate_table failed\n");
|
||
goto free_chainstack;
|
||
}
|
||
|
||
if (table->check && table->check(newinfo, table->valid_hooks)) {
|
||
BUGPRINT("The table doesn't like its own initial data, lol\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
table->private = newinfo;
|
||
rwlock_init(&table->lock);
|
||
ret = down_interruptible(&ebt_mutex);
|
||
if (ret != 0)
|
||
goto free_chainstack;
|
||
|
||
if (list_named_find(&ebt_tables, table->name)) {
|
||
ret = -EEXIST;
|
||
BUGPRINT("Table name already exists\n");
|
||
goto free_unlock;
|
||
}
|
||
|
||
/* Hold a reference count if the chains aren't empty */
|
||
if (newinfo->nentries && !try_module_get(table->me)) {
|
||
ret = -ENOENT;
|
||
goto free_unlock;
|
||
}
|
||
list_prepend(&ebt_tables, table);
|
||
up(&ebt_mutex);
|
||
return 0;
|
||
free_unlock:
|
||
up(&ebt_mutex);
|
||
free_chainstack:
|
||
if (newinfo->chainstack) {
|
||
for_each_cpu(i)
|
||
vfree(newinfo->chainstack[i]);
|
||
vfree(newinfo->chainstack);
|
||
}
|
||
vfree(newinfo->entries);
|
||
free_newinfo:
|
||
vfree(newinfo);
|
||
return ret;
|
||
}
|
||
|
||
void ebt_unregister_table(struct ebt_table *table)
|
||
{
|
||
int i;
|
||
|
||
if (!table) {
|
||
BUGPRINT("Request to unregister NULL table!!!\n");
|
||
return;
|
||
}
|
||
down(&ebt_mutex);
|
||
LIST_DELETE(&ebt_tables, table);
|
||
up(&ebt_mutex);
|
||
vfree(table->private->entries);
|
||
if (table->private->chainstack) {
|
||
for_each_cpu(i)
|
||
vfree(table->private->chainstack[i]);
|
||
vfree(table->private->chainstack);
|
||
}
|
||
vfree(table->private);
|
||
}
|
||
|
||
/* userspace just supplied us with counters */
|
||
static int update_counters(void __user *user, unsigned int len)
|
||
{
|
||
int i, ret;
|
||
struct ebt_counter *tmp;
|
||
struct ebt_replace hlp;
|
||
struct ebt_table *t;
|
||
|
||
if (copy_from_user(&hlp, user, sizeof(hlp)))
|
||
return -EFAULT;
|
||
|
||
if (len != sizeof(hlp) + hlp.num_counters * sizeof(struct ebt_counter))
|
||
return -EINVAL;
|
||
if (hlp.num_counters == 0)
|
||
return -EINVAL;
|
||
|
||
if ( !(tmp = (struct ebt_counter *)
|
||
vmalloc(hlp.num_counters * sizeof(struct ebt_counter))) ){
|
||
MEMPRINT("Update_counters && nomemory\n");
|
||
return -ENOMEM;
|
||
}
|
||
|
||
t = find_table_lock(hlp.name, &ret, &ebt_mutex);
|
||
if (!t)
|
||
goto free_tmp;
|
||
|
||
if (hlp.num_counters != t->private->nentries) {
|
||
BUGPRINT("Wrong nr of counters\n");
|
||
ret = -EINVAL;
|
||
goto unlock_mutex;
|
||
}
|
||
|
||
if ( copy_from_user(tmp, hlp.counters,
|
||
hlp.num_counters * sizeof(struct ebt_counter)) ) {
|
||
BUGPRINT("Updata_counters && !cfu\n");
|
||
ret = -EFAULT;
|
||
goto unlock_mutex;
|
||
}
|
||
|
||
/* we want an atomic add of the counters */
|
||
write_lock_bh(&t->lock);
|
||
|
||
/* we add to the counters of the first cpu */
|
||
for (i = 0; i < hlp.num_counters; i++) {
|
||
t->private->counters[i].pcnt += tmp[i].pcnt;
|
||
t->private->counters[i].bcnt += tmp[i].bcnt;
|
||
}
|
||
|
||
write_unlock_bh(&t->lock);
|
||
ret = 0;
|
||
unlock_mutex:
|
||
up(&ebt_mutex);
|
||
free_tmp:
|
||
vfree(tmp);
|
||
return ret;
|
||
}
|
||
|
||
static inline int ebt_make_matchname(struct ebt_entry_match *m,
|
||
char *base, char *ubase)
|
||
{
|
||
char *hlp = ubase - base + (char *)m;
|
||
if (copy_to_user(hlp, m->u.match->name, EBT_FUNCTION_MAXNAMELEN))
|
||
return -EFAULT;
|
||
return 0;
|
||
}
|
||
|
||
static inline int ebt_make_watchername(struct ebt_entry_watcher *w,
|
||
char *base, char *ubase)
|
||
{
|
||
char *hlp = ubase - base + (char *)w;
|
||
if (copy_to_user(hlp , w->u.watcher->name, EBT_FUNCTION_MAXNAMELEN))
|
||
return -EFAULT;
|
||
return 0;
|
||
}
|
||
|
||
static inline int ebt_make_names(struct ebt_entry *e, char *base, char *ubase)
|
||
{
|
||
int ret;
|
||
char *hlp;
|
||
struct ebt_entry_target *t;
|
||
|
||
if ((e->bitmask & EBT_ENTRY_OR_ENTRIES) == 0)
|
||
return 0;
|
||
|
||
hlp = ubase - base + (char *)e + e->target_offset;
|
||
t = (struct ebt_entry_target *)(((char *)e) + e->target_offset);
|
||
|
||
ret = EBT_MATCH_ITERATE(e, ebt_make_matchname, base, ubase);
|
||
if (ret != 0)
|
||
return ret;
|
||
ret = EBT_WATCHER_ITERATE(e, ebt_make_watchername, base, ubase);
|
||
if (ret != 0)
|
||
return ret;
|
||
if (copy_to_user(hlp, t->u.target->name, EBT_FUNCTION_MAXNAMELEN))
|
||
return -EFAULT;
|
||
return 0;
|
||
}
|
||
|
||
/* called with ebt_mutex down */
|
||
static int copy_everything_to_user(struct ebt_table *t, void __user *user,
|
||
int *len, int cmd)
|
||
{
|
||
struct ebt_replace tmp;
|
||
struct ebt_counter *counterstmp, *oldcounters;
|
||
unsigned int entries_size, nentries;
|
||
char *entries;
|
||
|
||
if (cmd == EBT_SO_GET_ENTRIES) {
|
||
entries_size = t->private->entries_size;
|
||
nentries = t->private->nentries;
|
||
entries = t->private->entries;
|
||
oldcounters = t->private->counters;
|
||
} else {
|
||
entries_size = t->table->entries_size;
|
||
nentries = t->table->nentries;
|
||
entries = t->table->entries;
|
||
oldcounters = t->table->counters;
|
||
}
|
||
|
||
if (copy_from_user(&tmp, user, sizeof(tmp))) {
|
||
BUGPRINT("Cfu didn't work\n");
|
||
return -EFAULT;
|
||
}
|
||
|
||
if (*len != sizeof(struct ebt_replace) + entries_size +
|
||
(tmp.num_counters? nentries * sizeof(struct ebt_counter): 0)) {
|
||
BUGPRINT("Wrong size\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
if (tmp.nentries != nentries) {
|
||
BUGPRINT("Nentries wrong\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
if (tmp.entries_size != entries_size) {
|
||
BUGPRINT("Wrong size\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
/* userspace might not need the counters */
|
||
if (tmp.num_counters) {
|
||
if (tmp.num_counters != nentries) {
|
||
BUGPRINT("Num_counters wrong\n");
|
||
return -EINVAL;
|
||
}
|
||
counterstmp = (struct ebt_counter *)
|
||
vmalloc(nentries * sizeof(struct ebt_counter));
|
||
if (!counterstmp) {
|
||
MEMPRINT("Couldn't copy counters, out of memory\n");
|
||
return -ENOMEM;
|
||
}
|
||
write_lock_bh(&t->lock);
|
||
get_counters(oldcounters, counterstmp, nentries);
|
||
write_unlock_bh(&t->lock);
|
||
|
||
if (copy_to_user(tmp.counters, counterstmp,
|
||
nentries * sizeof(struct ebt_counter))) {
|
||
BUGPRINT("Couldn't copy counters to userspace\n");
|
||
vfree(counterstmp);
|
||
return -EFAULT;
|
||
}
|
||
vfree(counterstmp);
|
||
}
|
||
|
||
if (copy_to_user(tmp.entries, entries, entries_size)) {
|
||
BUGPRINT("Couldn't copy entries to userspace\n");
|
||
return -EFAULT;
|
||
}
|
||
/* set the match/watcher/target names right */
|
||
return EBT_ENTRY_ITERATE(entries, entries_size,
|
||
ebt_make_names, entries, tmp.entries);
|
||
}
|
||
|
||
static int do_ebt_set_ctl(struct sock *sk,
|
||
int cmd, void __user *user, unsigned int len)
|
||
{
|
||
int ret;
|
||
|
||
switch(cmd) {
|
||
case EBT_SO_SET_ENTRIES:
|
||
ret = do_replace(user, len);
|
||
break;
|
||
case EBT_SO_SET_COUNTERS:
|
||
ret = update_counters(user, len);
|
||
break;
|
||
default:
|
||
ret = -EINVAL;
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
static int do_ebt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
|
||
{
|
||
int ret;
|
||
struct ebt_replace tmp;
|
||
struct ebt_table *t;
|
||
|
||
if (copy_from_user(&tmp, user, sizeof(tmp)))
|
||
return -EFAULT;
|
||
|
||
t = find_table_lock(tmp.name, &ret, &ebt_mutex);
|
||
if (!t)
|
||
return ret;
|
||
|
||
switch(cmd) {
|
||
case EBT_SO_GET_INFO:
|
||
case EBT_SO_GET_INIT_INFO:
|
||
if (*len != sizeof(struct ebt_replace)){
|
||
ret = -EINVAL;
|
||
up(&ebt_mutex);
|
||
break;
|
||
}
|
||
if (cmd == EBT_SO_GET_INFO) {
|
||
tmp.nentries = t->private->nentries;
|
||
tmp.entries_size = t->private->entries_size;
|
||
tmp.valid_hooks = t->valid_hooks;
|
||
} else {
|
||
tmp.nentries = t->table->nentries;
|
||
tmp.entries_size = t->table->entries_size;
|
||
tmp.valid_hooks = t->table->valid_hooks;
|
||
}
|
||
up(&ebt_mutex);
|
||
if (copy_to_user(user, &tmp, *len) != 0){
|
||
BUGPRINT("c2u Didn't work\n");
|
||
ret = -EFAULT;
|
||
break;
|
||
}
|
||
ret = 0;
|
||
break;
|
||
|
||
case EBT_SO_GET_ENTRIES:
|
||
case EBT_SO_GET_INIT_ENTRIES:
|
||
ret = copy_everything_to_user(t, user, len, cmd);
|
||
up(&ebt_mutex);
|
||
break;
|
||
|
||
default:
|
||
up(&ebt_mutex);
|
||
ret = -EINVAL;
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
static struct nf_sockopt_ops ebt_sockopts =
|
||
{ { NULL, NULL }, PF_INET, EBT_BASE_CTL, EBT_SO_SET_MAX + 1, do_ebt_set_ctl,
|
||
EBT_BASE_CTL, EBT_SO_GET_MAX + 1, do_ebt_get_ctl, 0, NULL
|
||
};
|
||
|
||
static int __init init(void)
|
||
{
|
||
int ret;
|
||
|
||
down(&ebt_mutex);
|
||
list_named_insert(&ebt_targets, &ebt_standard_target);
|
||
up(&ebt_mutex);
|
||
if ((ret = nf_register_sockopt(&ebt_sockopts)) < 0)
|
||
return ret;
|
||
|
||
printk(KERN_NOTICE "Ebtables v2.0 registered\n");
|
||
return 0;
|
||
}
|
||
|
||
static void __exit fini(void)
|
||
{
|
||
nf_unregister_sockopt(&ebt_sockopts);
|
||
printk(KERN_NOTICE "Ebtables v2.0 unregistered\n");
|
||
}
|
||
|
||
EXPORT_SYMBOL(ebt_register_table);
|
||
EXPORT_SYMBOL(ebt_unregister_table);
|
||
EXPORT_SYMBOL(ebt_register_match);
|
||
EXPORT_SYMBOL(ebt_unregister_match);
|
||
EXPORT_SYMBOL(ebt_register_watcher);
|
||
EXPORT_SYMBOL(ebt_unregister_watcher);
|
||
EXPORT_SYMBOL(ebt_register_target);
|
||
EXPORT_SYMBOL(ebt_unregister_target);
|
||
EXPORT_SYMBOL(ebt_do_table);
|
||
module_init(init);
|
||
module_exit(fini);
|
||
MODULE_LICENSE("GPL");
|