cfc2c74053
We had one report from syzkaller [1]
First issue is that INIT_WORK() should be done before mod_timer()
or we risk timer being fired too soon, even with a 1 second timer.
Second issue is that we need to reject too big info->timeout
to avoid overflows in msecs_to_jiffies(info->timeout * 1000), or
risk looping, if result after overflow is 0.
[1]
WARNING: CPU: 1 PID: 5129 at kernel/workqueue.c:1444 __queue_work+0xdf4/0x1230 kernel/workqueue.c:1444
Kernel panic - not syncing: panic_on_warn set ...
CPU: 1 PID: 5129 Comm: syzkaller159866 Not tainted 4.16.0-rc1+ #230
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
Call Trace:
<IRQ>
__dump_stack lib/dump_stack.c:17 [inline]
dump_stack+0x194/0x257 lib/dump_stack.c:53
panic+0x1e4/0x41c kernel/panic.c:183
__warn+0x1dc/0x200 kernel/panic.c:547
report_bug+0x211/0x2d0 lib/bug.c:184
fixup_bug.part.11+0x37/0x80 arch/x86/kernel/traps.c:178
fixup_bug arch/x86/kernel/traps.c:247 [inline]
do_error_trap+0x2d7/0x3e0 arch/x86/kernel/traps.c:296
do_invalid_op+0x1b/0x20 arch/x86/kernel/traps.c:315
invalid_op+0x22/0x40 arch/x86/entry/entry_64.S:988
RIP: 0010:__queue_work+0xdf4/0x1230 kernel/workqueue.c:1444
RSP: 0018:ffff8801db507538 EFLAGS: 00010006
RAX: ffff8801aeb46080 RBX: ffff8801db530200 RCX: ffffffff81481404
RDX: 0000000000000100 RSI: ffffffff86b42640 RDI: 0000000000000082
RBP: ffff8801db507758 R08: 1ffff1003b6a0de5 R09: 000000000000000c
R10: ffff8801db5073f0 R11: 0000000000000020 R12: 1ffff1003b6a0eb6
R13: ffff8801b1067ae0 R14: 00000000000001f8 R15: dffffc0000000000
queue_work_on+0x16a/0x1c0 kernel/workqueue.c:1488
queue_work include/linux/workqueue.h:488 [inline]
schedule_work include/linux/workqueue.h:546 [inline]
idletimer_tg_expired+0x44/0x60 net/netfilter/xt_IDLETIMER.c:116
call_timer_fn+0x228/0x820 kernel/time/timer.c:1326
expire_timers kernel/time/timer.c:1363 [inline]
__run_timers+0x7ee/0xb70 kernel/time/timer.c:1666
run_timer_softirq+0x4c/0x70 kernel/time/timer.c:1692
__do_softirq+0x2d7/0xb85 kernel/softirq.c:285
invoke_softirq kernel/softirq.c:365 [inline]
irq_exit+0x1cc/0x200 kernel/softirq.c:405
exiting_irq arch/x86/include/asm/apic.h:541 [inline]
smp_apic_timer_interrupt+0x16b/0x700 arch/x86/kernel/apic/apic.c:1052
apic_timer_interrupt+0xa9/0xb0 arch/x86/entry/entry_64.S:829
</IRQ>
RIP: 0010:arch_local_irq_restore arch/x86/include/asm/paravirt.h:777 [inline]
RIP: 0010:__raw_spin_unlock_irqrestore include/linux/spinlock_api_smp.h:160 [inline]
RIP: 0010:_raw_spin_unlock_irqrestore+0x5e/0xba kernel/locking/spinlock.c:184
RSP: 0018:ffff8801c20173c8 EFLAGS: 00000282 ORIG_RAX: ffffffffffffff12
RAX: dffffc0000000000 RBX: 0000000000000282 RCX: 0000000000000006
RDX: 1ffffffff0d592cd RSI: 1ffff10035d68d23 RDI: 0000000000000282
RBP: ffff8801c20173d8 R08: 1ffff10038402e47 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000000 R12: ffffffff8820e5c8
R13: ffff8801b1067ad8 R14: ffff8801aea7c268 R15: ffff8801aea7c278
__debug_object_init+0x235/0x1040 lib/debugobjects.c:378
debug_object_init+0x17/0x20 lib/debugobjects.c:391
__init_work+0x2b/0x60 kernel/workqueue.c:506
idletimer_tg_create net/netfilter/xt_IDLETIMER.c:152 [inline]
idletimer_tg_checkentry+0x691/0xb00 net/netfilter/xt_IDLETIMER.c:213
xt_check_target+0x22c/0x7d0 net/netfilter/x_tables.c:850
check_target net/ipv6/netfilter/ip6_tables.c:533 [inline]
find_check_entry.isra.7+0x935/0xcf0 net/ipv6/netfilter/ip6_tables.c:575
translate_table+0xf52/0x1690 net/ipv6/netfilter/ip6_tables.c:744
do_replace net/ipv6/netfilter/ip6_tables.c:1160 [inline]
do_ip6t_set_ctl+0x370/0x5f0 net/ipv6/netfilter/ip6_tables.c:1686
nf_sockopt net/netfilter/nf_sockopt.c:106 [inline]
nf_setsockopt+0x67/0xc0 net/netfilter/nf_sockopt.c:115
ipv6_setsockopt+0x10b/0x130 net/ipv6/ipv6_sockglue.c:927
udpv6_setsockopt+0x45/0x80 net/ipv6/udp.c:1422
sock_common_setsockopt+0x95/0xd0 net/core/sock.c:2976
SYSC_setsockopt net/socket.c:1850 [inline]
SyS_setsockopt+0x189/0x360 net/socket.c:1829
do_syscall_64+0x282/0x940 arch/x86/entry/common.c:287
Fixes: 0902b469bd
("netfilter: xtables: idletimer target implementation")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: syzkaller <syzkaller@googlegroups.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
321 lines
7.7 KiB
C
321 lines
7.7 KiB
C
/*
|
|
* linux/net/netfilter/xt_IDLETIMER.c
|
|
*
|
|
* Netfilter module to trigger a timer when packet matches.
|
|
* After timer expires a kevent will be sent.
|
|
*
|
|
* Copyright (C) 2004, 2010 Nokia Corporation
|
|
* Written by Timo Teras <ext-timo.teras@nokia.com>
|
|
*
|
|
* Converted to x_tables and reworked for upstream inclusion
|
|
* by Luciano Coelho <luciano.coelho@nokia.com>
|
|
*
|
|
* Contact: Luciano Coelho <luciano.coelho@nokia.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/netfilter.h>
|
|
#include <linux/netfilter/x_tables.h>
|
|
#include <linux/netfilter/xt_IDLETIMER.h>
|
|
#include <linux/kdev_t.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/sysfs.h>
|
|
|
|
struct idletimer_tg_attr {
|
|
struct attribute attr;
|
|
ssize_t (*show)(struct kobject *kobj,
|
|
struct attribute *attr, char *buf);
|
|
};
|
|
|
|
struct idletimer_tg {
|
|
struct list_head entry;
|
|
struct timer_list timer;
|
|
struct work_struct work;
|
|
|
|
struct kobject *kobj;
|
|
struct idletimer_tg_attr attr;
|
|
|
|
unsigned int refcnt;
|
|
};
|
|
|
|
static LIST_HEAD(idletimer_tg_list);
|
|
static DEFINE_MUTEX(list_mutex);
|
|
|
|
static struct kobject *idletimer_tg_kobj;
|
|
|
|
static
|
|
struct idletimer_tg *__idletimer_tg_find_by_label(const char *label)
|
|
{
|
|
struct idletimer_tg *entry;
|
|
|
|
BUG_ON(!label);
|
|
|
|
list_for_each_entry(entry, &idletimer_tg_list, entry) {
|
|
if (!strcmp(label, entry->attr.attr.name))
|
|
return entry;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ssize_t idletimer_tg_show(struct kobject *kobj, struct attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct idletimer_tg *timer;
|
|
unsigned long expires = 0;
|
|
|
|
mutex_lock(&list_mutex);
|
|
|
|
timer = __idletimer_tg_find_by_label(attr->name);
|
|
if (timer)
|
|
expires = timer->timer.expires;
|
|
|
|
mutex_unlock(&list_mutex);
|
|
|
|
if (time_after(expires, jiffies))
|
|
return sprintf(buf, "%u\n",
|
|
jiffies_to_msecs(expires - jiffies) / 1000);
|
|
|
|
return sprintf(buf, "0\n");
|
|
}
|
|
|
|
static void idletimer_tg_work(struct work_struct *work)
|
|
{
|
|
struct idletimer_tg *timer = container_of(work, struct idletimer_tg,
|
|
work);
|
|
|
|
sysfs_notify(idletimer_tg_kobj, NULL, timer->attr.attr.name);
|
|
}
|
|
|
|
static void idletimer_tg_expired(struct timer_list *t)
|
|
{
|
|
struct idletimer_tg *timer = from_timer(timer, t, timer);
|
|
|
|
pr_debug("timer %s expired\n", timer->attr.attr.name);
|
|
|
|
schedule_work(&timer->work);
|
|
}
|
|
|
|
static int idletimer_tg_create(struct idletimer_tg_info *info)
|
|
{
|
|
int ret;
|
|
|
|
info->timer = kmalloc(sizeof(*info->timer), GFP_KERNEL);
|
|
if (!info->timer) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
sysfs_attr_init(&info->timer->attr.attr);
|
|
info->timer->attr.attr.name = kstrdup(info->label, GFP_KERNEL);
|
|
if (!info->timer->attr.attr.name) {
|
|
ret = -ENOMEM;
|
|
goto out_free_timer;
|
|
}
|
|
info->timer->attr.attr.mode = S_IRUGO;
|
|
info->timer->attr.show = idletimer_tg_show;
|
|
|
|
ret = sysfs_create_file(idletimer_tg_kobj, &info->timer->attr.attr);
|
|
if (ret < 0) {
|
|
pr_debug("couldn't add file to sysfs");
|
|
goto out_free_attr;
|
|
}
|
|
|
|
list_add(&info->timer->entry, &idletimer_tg_list);
|
|
|
|
timer_setup(&info->timer->timer, idletimer_tg_expired, 0);
|
|
info->timer->refcnt = 1;
|
|
|
|
INIT_WORK(&info->timer->work, idletimer_tg_work);
|
|
|
|
mod_timer(&info->timer->timer,
|
|
msecs_to_jiffies(info->timeout * 1000) + jiffies);
|
|
|
|
return 0;
|
|
|
|
out_free_attr:
|
|
kfree(info->timer->attr.attr.name);
|
|
out_free_timer:
|
|
kfree(info->timer);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* The actual xt_tables plugin.
|
|
*/
|
|
static unsigned int idletimer_tg_target(struct sk_buff *skb,
|
|
const struct xt_action_param *par)
|
|
{
|
|
const struct idletimer_tg_info *info = par->targinfo;
|
|
|
|
pr_debug("resetting timer %s, timeout period %u\n",
|
|
info->label, info->timeout);
|
|
|
|
BUG_ON(!info->timer);
|
|
|
|
mod_timer(&info->timer->timer,
|
|
msecs_to_jiffies(info->timeout * 1000) + jiffies);
|
|
|
|
return XT_CONTINUE;
|
|
}
|
|
|
|
static int idletimer_tg_checkentry(const struct xt_tgchk_param *par)
|
|
{
|
|
struct idletimer_tg_info *info = par->targinfo;
|
|
int ret;
|
|
|
|
pr_debug("checkentry targinfo%s\n", info->label);
|
|
|
|
if (info->timeout == 0) {
|
|
pr_debug("timeout value is zero\n");
|
|
return -EINVAL;
|
|
}
|
|
if (info->timeout >= INT_MAX / 1000) {
|
|
pr_debug("timeout value is too big\n");
|
|
return -EINVAL;
|
|
}
|
|
if (info->label[0] == '\0' ||
|
|
strnlen(info->label,
|
|
MAX_IDLETIMER_LABEL_SIZE) == MAX_IDLETIMER_LABEL_SIZE) {
|
|
pr_debug("label is empty or not nul-terminated\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&list_mutex);
|
|
|
|
info->timer = __idletimer_tg_find_by_label(info->label);
|
|
if (info->timer) {
|
|
info->timer->refcnt++;
|
|
mod_timer(&info->timer->timer,
|
|
msecs_to_jiffies(info->timeout * 1000) + jiffies);
|
|
|
|
pr_debug("increased refcnt of timer %s to %u\n",
|
|
info->label, info->timer->refcnt);
|
|
} else {
|
|
ret = idletimer_tg_create(info);
|
|
if (ret < 0) {
|
|
pr_debug("failed to create timer\n");
|
|
mutex_unlock(&list_mutex);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&list_mutex);
|
|
return 0;
|
|
}
|
|
|
|
static void idletimer_tg_destroy(const struct xt_tgdtor_param *par)
|
|
{
|
|
const struct idletimer_tg_info *info = par->targinfo;
|
|
|
|
pr_debug("destroy targinfo %s\n", info->label);
|
|
|
|
mutex_lock(&list_mutex);
|
|
|
|
if (--info->timer->refcnt == 0) {
|
|
pr_debug("deleting timer %s\n", info->label);
|
|
|
|
list_del(&info->timer->entry);
|
|
del_timer_sync(&info->timer->timer);
|
|
cancel_work_sync(&info->timer->work);
|
|
sysfs_remove_file(idletimer_tg_kobj, &info->timer->attr.attr);
|
|
kfree(info->timer->attr.attr.name);
|
|
kfree(info->timer);
|
|
} else {
|
|
pr_debug("decreased refcnt of timer %s to %u\n",
|
|
info->label, info->timer->refcnt);
|
|
}
|
|
|
|
mutex_unlock(&list_mutex);
|
|
}
|
|
|
|
static struct xt_target idletimer_tg __read_mostly = {
|
|
.name = "IDLETIMER",
|
|
.family = NFPROTO_UNSPEC,
|
|
.target = idletimer_tg_target,
|
|
.targetsize = sizeof(struct idletimer_tg_info),
|
|
.usersize = offsetof(struct idletimer_tg_info, timer),
|
|
.checkentry = idletimer_tg_checkentry,
|
|
.destroy = idletimer_tg_destroy,
|
|
.me = THIS_MODULE,
|
|
};
|
|
|
|
static struct class *idletimer_tg_class;
|
|
|
|
static struct device *idletimer_tg_device;
|
|
|
|
static int __init idletimer_tg_init(void)
|
|
{
|
|
int err;
|
|
|
|
idletimer_tg_class = class_create(THIS_MODULE, "xt_idletimer");
|
|
err = PTR_ERR(idletimer_tg_class);
|
|
if (IS_ERR(idletimer_tg_class)) {
|
|
pr_debug("couldn't register device class\n");
|
|
goto out;
|
|
}
|
|
|
|
idletimer_tg_device = device_create(idletimer_tg_class, NULL,
|
|
MKDEV(0, 0), NULL, "timers");
|
|
err = PTR_ERR(idletimer_tg_device);
|
|
if (IS_ERR(idletimer_tg_device)) {
|
|
pr_debug("couldn't register system device\n");
|
|
goto out_class;
|
|
}
|
|
|
|
idletimer_tg_kobj = &idletimer_tg_device->kobj;
|
|
|
|
err = xt_register_target(&idletimer_tg);
|
|
if (err < 0) {
|
|
pr_debug("couldn't register xt target\n");
|
|
goto out_dev;
|
|
}
|
|
|
|
return 0;
|
|
out_dev:
|
|
device_destroy(idletimer_tg_class, MKDEV(0, 0));
|
|
out_class:
|
|
class_destroy(idletimer_tg_class);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static void __exit idletimer_tg_exit(void)
|
|
{
|
|
xt_unregister_target(&idletimer_tg);
|
|
|
|
device_destroy(idletimer_tg_class, MKDEV(0, 0));
|
|
class_destroy(idletimer_tg_class);
|
|
}
|
|
|
|
module_init(idletimer_tg_init);
|
|
module_exit(idletimer_tg_exit);
|
|
|
|
MODULE_AUTHOR("Timo Teras <ext-timo.teras@nokia.com>");
|
|
MODULE_AUTHOR("Luciano Coelho <luciano.coelho@nokia.com>");
|
|
MODULE_DESCRIPTION("Xtables: idle time monitor");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("ipt_IDLETIMER");
|
|
MODULE_ALIAS("ip6t_IDLETIMER");
|