mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-05-14 11:42:57 +00:00

Binding AX25 socket by using the autobind feature leads to memory leaks
in ax25_connect() and also refcount leaks in ax25_release(). Memory
leak was detected with kmemleak:
================================================================
unreferenced object 0xffff8880253cd680 (size 96):
backtrace:
__kmalloc_node_track_caller_noprof (./include/linux/kmemleak.h:43)
kmemdup_noprof (mm/util.c:136)
ax25_rt_autobind (net/ax25/ax25_route.c:428)
ax25_connect (net/ax25/af_ax25.c:1282)
__sys_connect_file (net/socket.c:2045)
__sys_connect (net/socket.c:2064)
__x64_sys_connect (net/socket.c:2067)
do_syscall_64 (arch/x86/entry/common.c:52 arch/x86/entry/common.c:83)
entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
================================================================
When socket is bound, refcounts must be incremented the way it is done
in ax25_bind() and ax25_setsockopt() (SO_BINDTODEVICE). In case of
autobind, the refcounts are not incremented.
This bug leads to the following issue reported by Syzkaller:
================================================================
ax25_connect(): syz-executor318 uses autobind, please contact jreuter@yaina.de
------------[ cut here ]------------
refcount_t: decrement hit 0; leaking memory.
WARNING: CPU: 0 PID: 5317 at lib/refcount.c:31 refcount_warn_saturate+0xfa/0x1d0 lib/refcount.c:31
Modules linked in:
CPU: 0 UID: 0 PID: 5317 Comm: syz-executor318 Not tainted 6.14.0-rc4-syzkaller-00278-gece144f151ac #0
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2~bpo12+1 04/01/2014
RIP: 0010:refcount_warn_saturate+0xfa/0x1d0 lib/refcount.c:31
...
Call Trace:
<TASK>
__refcount_dec include/linux/refcount.h:336 [inline]
refcount_dec include/linux/refcount.h:351 [inline]
ref_tracker_free+0x6af/0x7e0 lib/ref_tracker.c:236
netdev_tracker_free include/linux/netdevice.h:4302 [inline]
netdev_put include/linux/netdevice.h:4319 [inline]
ax25_release+0x368/0x960 net/ax25/af_ax25.c:1080
__sock_release net/socket.c:647 [inline]
sock_close+0xbc/0x240 net/socket.c:1398
__fput+0x3e9/0x9f0 fs/file_table.c:464
__do_sys_close fs/open.c:1580 [inline]
__se_sys_close fs/open.c:1565 [inline]
__x64_sys_close+0x7f/0x110 fs/open.c:1565
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xf3/0x230 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
...
</TASK>
================================================================
Considering the issues above and the comments left in the code that say:
"check if we can remove this feature. It is broken."; "autobinding in this
may or may not work"; - it is better to completely remove this feature than
to fix it because it is broken and leads to various kinds of memory bugs.
Now calling connect() without first binding socket will result in an
error (-EINVAL). Userspace software that relies on the autobind feature
might get broken. However, this feature does not seem widely used with
this specific driver as it was not reliable at any point of time, and it
is already broken anyway. E.g. ax25-tools and ax25-apps packages for
popular distributions do not use the autobind feature for AF_AX25.
Found by Linux Verification Center (linuxtesting.org) with Syzkaller.
Fixes: 1da177e4c3
("Linux-2.6.12-rc2")
Reported-by: syzbot+33841dc6aa3e1d86b78a@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=33841dc6aa3e1d86b78a
Signed-off-by: Murad Masimov <m.masimov@mt-integration.ru>
Signed-off-by: David S. Miller <davem@davemloft.net>
416 lines
9.3 KiB
C
416 lines
9.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
*
|
|
* Copyright (C) Alan Cox GW4PTS (alan@lxorguk.ukuu.org.uk)
|
|
* Copyright (C) Jonathan Naylor G4KLX (g4klx@g4klx.demon.co.uk)
|
|
* Copyright (C) Steven Whitehouse GW7RRM (stevew@acm.org)
|
|
* Copyright (C) Joerg Reuter DL1BKE (jreuter@yaina.de)
|
|
* Copyright (C) Hans-Joachim Hetscher DD8NE (dd8ne@bnv-bamberg.de)
|
|
* Copyright (C) Frederic Rible F1OAT (frible@teaser.fr)
|
|
*/
|
|
|
|
#include <linux/capability.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/types.h>
|
|
#include <linux/socket.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/in.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/string.h>
|
|
#include <linux/sockios.h>
|
|
#include <linux/net.h>
|
|
#include <linux/slab.h>
|
|
#include <net/ax25.h>
|
|
#include <linux/inet.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/if_arp.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/spinlock.h>
|
|
#include <net/sock.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/fcntl.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/init.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/export.h>
|
|
|
|
static ax25_route *ax25_route_list;
|
|
DEFINE_RWLOCK(ax25_route_lock);
|
|
|
|
void ax25_rt_device_down(struct net_device *dev)
|
|
{
|
|
ax25_route *s, *t, *ax25_rt;
|
|
|
|
write_lock_bh(&ax25_route_lock);
|
|
ax25_rt = ax25_route_list;
|
|
while (ax25_rt != NULL) {
|
|
s = ax25_rt;
|
|
ax25_rt = ax25_rt->next;
|
|
|
|
if (s->dev == dev) {
|
|
if (ax25_route_list == s) {
|
|
ax25_route_list = s->next;
|
|
kfree(s->digipeat);
|
|
kfree(s);
|
|
} else {
|
|
for (t = ax25_route_list; t != NULL; t = t->next) {
|
|
if (t->next == s) {
|
|
t->next = s->next;
|
|
kfree(s->digipeat);
|
|
kfree(s);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
write_unlock_bh(&ax25_route_lock);
|
|
}
|
|
|
|
static int __must_check ax25_rt_add(struct ax25_routes_struct *route)
|
|
{
|
|
ax25_route *ax25_rt;
|
|
ax25_dev *ax25_dev;
|
|
int i;
|
|
|
|
if (route->digi_count > AX25_MAX_DIGIS)
|
|
return -EINVAL;
|
|
|
|
ax25_dev = ax25_addr_ax25dev(&route->port_addr);
|
|
if (!ax25_dev)
|
|
return -EINVAL;
|
|
|
|
write_lock_bh(&ax25_route_lock);
|
|
|
|
ax25_rt = ax25_route_list;
|
|
while (ax25_rt != NULL) {
|
|
if (ax25cmp(&ax25_rt->callsign, &route->dest_addr) == 0 &&
|
|
ax25_rt->dev == ax25_dev->dev) {
|
|
kfree(ax25_rt->digipeat);
|
|
ax25_rt->digipeat = NULL;
|
|
if (route->digi_count != 0) {
|
|
if ((ax25_rt->digipeat = kmalloc(sizeof(ax25_digi), GFP_ATOMIC)) == NULL) {
|
|
write_unlock_bh(&ax25_route_lock);
|
|
ax25_dev_put(ax25_dev);
|
|
return -ENOMEM;
|
|
}
|
|
ax25_rt->digipeat->lastrepeat = -1;
|
|
ax25_rt->digipeat->ndigi = route->digi_count;
|
|
for (i = 0; i < route->digi_count; i++) {
|
|
ax25_rt->digipeat->repeated[i] = 0;
|
|
ax25_rt->digipeat->calls[i] = route->digi_addr[i];
|
|
}
|
|
}
|
|
write_unlock_bh(&ax25_route_lock);
|
|
ax25_dev_put(ax25_dev);
|
|
return 0;
|
|
}
|
|
ax25_rt = ax25_rt->next;
|
|
}
|
|
|
|
if ((ax25_rt = kmalloc(sizeof(ax25_route), GFP_ATOMIC)) == NULL) {
|
|
write_unlock_bh(&ax25_route_lock);
|
|
ax25_dev_put(ax25_dev);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ax25_rt->callsign = route->dest_addr;
|
|
ax25_rt->dev = ax25_dev->dev;
|
|
ax25_rt->digipeat = NULL;
|
|
ax25_rt->ip_mode = ' ';
|
|
if (route->digi_count != 0) {
|
|
if ((ax25_rt->digipeat = kmalloc(sizeof(ax25_digi), GFP_ATOMIC)) == NULL) {
|
|
write_unlock_bh(&ax25_route_lock);
|
|
kfree(ax25_rt);
|
|
ax25_dev_put(ax25_dev);
|
|
return -ENOMEM;
|
|
}
|
|
ax25_rt->digipeat->lastrepeat = -1;
|
|
ax25_rt->digipeat->ndigi = route->digi_count;
|
|
for (i = 0; i < route->digi_count; i++) {
|
|
ax25_rt->digipeat->repeated[i] = 0;
|
|
ax25_rt->digipeat->calls[i] = route->digi_addr[i];
|
|
}
|
|
}
|
|
ax25_rt->next = ax25_route_list;
|
|
ax25_route_list = ax25_rt;
|
|
write_unlock_bh(&ax25_route_lock);
|
|
ax25_dev_put(ax25_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void __ax25_put_route(ax25_route *ax25_rt)
|
|
{
|
|
kfree(ax25_rt->digipeat);
|
|
kfree(ax25_rt);
|
|
}
|
|
|
|
static int ax25_rt_del(struct ax25_routes_struct *route)
|
|
{
|
|
ax25_route *s, *t, *ax25_rt;
|
|
ax25_dev *ax25_dev;
|
|
|
|
if ((ax25_dev = ax25_addr_ax25dev(&route->port_addr)) == NULL)
|
|
return -EINVAL;
|
|
|
|
write_lock_bh(&ax25_route_lock);
|
|
|
|
ax25_rt = ax25_route_list;
|
|
while (ax25_rt != NULL) {
|
|
s = ax25_rt;
|
|
ax25_rt = ax25_rt->next;
|
|
if (s->dev == ax25_dev->dev &&
|
|
ax25cmp(&route->dest_addr, &s->callsign) == 0) {
|
|
if (ax25_route_list == s) {
|
|
ax25_route_list = s->next;
|
|
__ax25_put_route(s);
|
|
} else {
|
|
for (t = ax25_route_list; t != NULL; t = t->next) {
|
|
if (t->next == s) {
|
|
t->next = s->next;
|
|
__ax25_put_route(s);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
write_unlock_bh(&ax25_route_lock);
|
|
ax25_dev_put(ax25_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ax25_rt_opt(struct ax25_route_opt_struct *rt_option)
|
|
{
|
|
ax25_route *ax25_rt;
|
|
ax25_dev *ax25_dev;
|
|
int err = 0;
|
|
|
|
if ((ax25_dev = ax25_addr_ax25dev(&rt_option->port_addr)) == NULL)
|
|
return -EINVAL;
|
|
|
|
write_lock_bh(&ax25_route_lock);
|
|
|
|
ax25_rt = ax25_route_list;
|
|
while (ax25_rt != NULL) {
|
|
if (ax25_rt->dev == ax25_dev->dev &&
|
|
ax25cmp(&rt_option->dest_addr, &ax25_rt->callsign) == 0) {
|
|
switch (rt_option->cmd) {
|
|
case AX25_SET_RT_IPMODE:
|
|
switch (rt_option->arg) {
|
|
case ' ':
|
|
case 'D':
|
|
case 'V':
|
|
ax25_rt->ip_mode = rt_option->arg;
|
|
break;
|
|
default:
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
break;
|
|
default:
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
}
|
|
ax25_rt = ax25_rt->next;
|
|
}
|
|
|
|
out:
|
|
write_unlock_bh(&ax25_route_lock);
|
|
ax25_dev_put(ax25_dev);
|
|
return err;
|
|
}
|
|
|
|
int ax25_rt_ioctl(unsigned int cmd, void __user *arg)
|
|
{
|
|
struct ax25_route_opt_struct rt_option;
|
|
struct ax25_routes_struct route;
|
|
|
|
switch (cmd) {
|
|
case SIOCADDRT:
|
|
if (copy_from_user(&route, arg, sizeof(route)))
|
|
return -EFAULT;
|
|
return ax25_rt_add(&route);
|
|
|
|
case SIOCDELRT:
|
|
if (copy_from_user(&route, arg, sizeof(route)))
|
|
return -EFAULT;
|
|
return ax25_rt_del(&route);
|
|
|
|
case SIOCAX25OPTRT:
|
|
if (copy_from_user(&rt_option, arg, sizeof(rt_option)))
|
|
return -EFAULT;
|
|
return ax25_rt_opt(&rt_option);
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
|
|
static void *ax25_rt_seq_start(struct seq_file *seq, loff_t *pos)
|
|
__acquires(ax25_route_lock)
|
|
{
|
|
struct ax25_route *ax25_rt;
|
|
int i = 1;
|
|
|
|
read_lock(&ax25_route_lock);
|
|
if (*pos == 0)
|
|
return SEQ_START_TOKEN;
|
|
|
|
for (ax25_rt = ax25_route_list; ax25_rt != NULL; ax25_rt = ax25_rt->next) {
|
|
if (i == *pos)
|
|
return ax25_rt;
|
|
++i;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void *ax25_rt_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
++*pos;
|
|
return (v == SEQ_START_TOKEN) ? ax25_route_list :
|
|
((struct ax25_route *) v)->next;
|
|
}
|
|
|
|
static void ax25_rt_seq_stop(struct seq_file *seq, void *v)
|
|
__releases(ax25_route_lock)
|
|
{
|
|
read_unlock(&ax25_route_lock);
|
|
}
|
|
|
|
static int ax25_rt_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
char buf[11];
|
|
|
|
if (v == SEQ_START_TOKEN)
|
|
seq_puts(seq, "callsign dev mode digipeaters\n");
|
|
else {
|
|
struct ax25_route *ax25_rt = v;
|
|
const char *callsign;
|
|
int i;
|
|
|
|
if (ax25cmp(&ax25_rt->callsign, &null_ax25_address) == 0)
|
|
callsign = "default";
|
|
else
|
|
callsign = ax2asc(buf, &ax25_rt->callsign);
|
|
|
|
seq_printf(seq, "%-9s %-4s",
|
|
callsign,
|
|
ax25_rt->dev ? ax25_rt->dev->name : "???");
|
|
|
|
switch (ax25_rt->ip_mode) {
|
|
case 'V':
|
|
seq_puts(seq, " vc");
|
|
break;
|
|
case 'D':
|
|
seq_puts(seq, " dg");
|
|
break;
|
|
default:
|
|
seq_puts(seq, " *");
|
|
break;
|
|
}
|
|
|
|
if (ax25_rt->digipeat != NULL)
|
|
for (i = 0; i < ax25_rt->digipeat->ndigi; i++)
|
|
seq_printf(seq, " %s",
|
|
ax2asc(buf, &ax25_rt->digipeat->calls[i]));
|
|
|
|
seq_puts(seq, "\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const struct seq_operations ax25_rt_seqops = {
|
|
.start = ax25_rt_seq_start,
|
|
.next = ax25_rt_seq_next,
|
|
.stop = ax25_rt_seq_stop,
|
|
.show = ax25_rt_seq_show,
|
|
};
|
|
#endif
|
|
|
|
/*
|
|
* Find AX.25 route
|
|
*
|
|
* Only routes with a reference count of zero can be destroyed.
|
|
* Must be called with ax25_route_lock read locked.
|
|
*/
|
|
ax25_route *ax25_get_route(ax25_address *addr, struct net_device *dev)
|
|
{
|
|
ax25_route *ax25_spe_rt = NULL;
|
|
ax25_route *ax25_def_rt = NULL;
|
|
ax25_route *ax25_rt;
|
|
|
|
/*
|
|
* Bind to the physical interface we heard them on, or the default
|
|
* route if none is found;
|
|
*/
|
|
for (ax25_rt = ax25_route_list; ax25_rt != NULL; ax25_rt = ax25_rt->next) {
|
|
if (dev == NULL) {
|
|
if (ax25cmp(&ax25_rt->callsign, addr) == 0 && ax25_rt->dev != NULL)
|
|
ax25_spe_rt = ax25_rt;
|
|
if (ax25cmp(&ax25_rt->callsign, &null_ax25_address) == 0 && ax25_rt->dev != NULL)
|
|
ax25_def_rt = ax25_rt;
|
|
} else {
|
|
if (ax25cmp(&ax25_rt->callsign, addr) == 0 && ax25_rt->dev == dev)
|
|
ax25_spe_rt = ax25_rt;
|
|
if (ax25cmp(&ax25_rt->callsign, &null_ax25_address) == 0 && ax25_rt->dev == dev)
|
|
ax25_def_rt = ax25_rt;
|
|
}
|
|
}
|
|
|
|
ax25_rt = ax25_def_rt;
|
|
if (ax25_spe_rt != NULL)
|
|
ax25_rt = ax25_spe_rt;
|
|
|
|
return ax25_rt;
|
|
}
|
|
|
|
|
|
struct sk_buff *ax25_rt_build_path(struct sk_buff *skb, ax25_address *src,
|
|
ax25_address *dest, ax25_digi *digi)
|
|
{
|
|
unsigned char *bp;
|
|
int len;
|
|
|
|
len = digi->ndigi * AX25_ADDR_LEN;
|
|
|
|
if (unlikely(skb_headroom(skb) < len)) {
|
|
skb = skb_expand_head(skb, len);
|
|
if (!skb) {
|
|
printk(KERN_CRIT "AX.25: ax25_dg_build_path - out of memory\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
bp = skb_push(skb, len);
|
|
|
|
ax25_addr_build(bp, src, dest, digi, AX25_COMMAND, AX25_MODULUS);
|
|
|
|
return skb;
|
|
}
|
|
|
|
/*
|
|
* Free all memory associated with routing structures.
|
|
*/
|
|
void __exit ax25_rt_free(void)
|
|
{
|
|
ax25_route *s, *ax25_rt = ax25_route_list;
|
|
|
|
write_lock_bh(&ax25_route_lock);
|
|
while (ax25_rt != NULL) {
|
|
s = ax25_rt;
|
|
ax25_rt = ax25_rt->next;
|
|
|
|
kfree(s->digipeat);
|
|
kfree(s);
|
|
}
|
|
write_unlock_bh(&ax25_route_lock);
|
|
}
|