Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 29303547 authored by Vlad Yasevich's avatar Vlad Yasevich Committed by David S. Miller
Browse files

[SCTP]: Add RCU synchronization around sctp_localaddr_list



sctp_localaddr_list is modified dynamically via NETDEV_UP
and NETDEV_DOWN events, but there is not synchronization
between writer (even handler) and readers.  As a result,
the readers can access an entry that has been freed and
crash the sytem.

Signed-off-by: default avatarVlad Yasevich <vladislav.yasevich@hp.com>
Acked-by: default avatarPaul E. McKenney <paulmck@linux.vnet.ibm.com>
Acked-by: default avatarSridhar Samdurala <sri@us.ibm.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent ddeee3ce
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -123,6 +123,7 @@
 * sctp/protocol.c
 */
extern struct sock *sctp_get_ctl_sock(void);
extern void sctp_local_addr_free(struct rcu_head *head);
extern int sctp_copy_local_addr_list(struct sctp_bind_addr *,
				     sctp_scope_t, gfp_t gfp,
				     int flags);
+6 −0
Original line number Diff line number Diff line
@@ -208,6 +208,9 @@ extern struct sctp_globals {
	 */
	struct list_head local_addr_list;

	/* Lock that protects the local_addr_list writers */
	spinlock_t addr_list_lock;
	
	/* Flag to indicate if addip is enabled. */
	int addip_enable;

@@ -242,6 +245,7 @@ extern struct sctp_globals {
#define sctp_port_alloc_lock		(sctp_globals.port_alloc_lock)
#define sctp_port_hashtable		(sctp_globals.port_hashtable)
#define sctp_local_addr_list		(sctp_globals.local_addr_list)
#define sctp_local_addr_lock		(sctp_globals.addr_list_lock)
#define sctp_addip_enable		(sctp_globals.addip_enable)
#define sctp_prsctp_enable		(sctp_globals.prsctp_enable)

@@ -737,8 +741,10 @@ const union sctp_addr *sctp_source(const struct sctp_chunk *chunk);
/* This is a structure for holding either an IPv6 or an IPv4 address.  */
struct sctp_sockaddr_entry {
	struct list_head list;
	struct rcu_head	rcu;
	union sctp_addr a;
	__u8 use_as_src;
	__u8 valid;
};

typedef struct sctp_chunk *(sctp_packet_phandler_t)(struct sctp_association *);
+2 −0
Original line number Diff line number Diff line
@@ -163,8 +163,10 @@ int sctp_add_bind_addr(struct sctp_bind_addr *bp, union sctp_addr *new,
		addr->a.v4.sin_port = htons(bp->port);

	addr->use_as_src = use_as_src;
	addr->valid = 1;

	INIT_LIST_HEAD(&addr->list);
	INIT_RCU_HEAD(&addr->rcu);
	list_add_tail(&addr->list, &bp->address_list);
	SCTP_DBG_OBJCNT_INC(addr);

+24 −10
Original line number Diff line number Diff line
@@ -77,13 +77,18 @@

#include <asm/uaccess.h>

/* Event handler for inet6 address addition/deletion events.  */
/* Event handler for inet6 address addition/deletion events.
 * The sctp_local_addr_list needs to be protocted by a spin lock since
 * multiple notifiers (say IPv4 and IPv6) may be running at the same
 * time and thus corrupt the list.
 * The reader side is protected with RCU.
 */
static int sctp_inet6addr_event(struct notifier_block *this, unsigned long ev,
				void *ptr)
{
	struct inet6_ifaddr *ifa = (struct inet6_ifaddr *)ptr;
	struct sctp_sockaddr_entry *addr;
	struct list_head *pos, *temp;
	struct sctp_sockaddr_entry *addr = NULL;
	struct sctp_sockaddr_entry *temp;

	switch (ev) {
	case NETDEV_UP:
@@ -94,19 +99,26 @@ static int sctp_inet6addr_event(struct notifier_block *this, unsigned long ev,
			memcpy(&addr->a.v6.sin6_addr, &ifa->addr,
				 sizeof(struct in6_addr));
			addr->a.v6.sin6_scope_id = ifa->idev->dev->ifindex;
			list_add_tail(&addr->list, &sctp_local_addr_list);
			addr->valid = 1;
			spin_lock_bh(&sctp_local_addr_lock);
			list_add_tail_rcu(&addr->list, &sctp_local_addr_list);
			spin_unlock_bh(&sctp_local_addr_lock);
		}
		break;
	case NETDEV_DOWN:
		list_for_each_safe(pos, temp, &sctp_local_addr_list) {
			addr = list_entry(pos, struct sctp_sockaddr_entry, list);
			if (ipv6_addr_equal(&addr->a.v6.sin6_addr, &ifa->addr)) {
				list_del(pos);
				kfree(addr);
		spin_lock_bh(&sctp_local_addr_lock);
		list_for_each_entry_safe(addr, temp,
					&sctp_local_addr_list, list) {
			if (ipv6_addr_equal(&addr->a.v6.sin6_addr,
					     &ifa->addr)) {
				addr->valid = 0;
				list_del_rcu(&addr->list);
				break;
			}
		}

		spin_unlock_bh(&sctp_local_addr_lock);
		if (addr && !addr->valid)
			call_rcu(&addr->rcu, sctp_local_addr_free);
		break;
	}

@@ -367,7 +379,9 @@ static void sctp_v6_copy_addrlist(struct list_head *addrlist,
			addr->a.v6.sin6_port = 0;
			addr->a.v6.sin6_addr = ifp->addr;
			addr->a.v6.sin6_scope_id = dev->ifindex;
			addr->valid = 1;
			INIT_LIST_HEAD(&addr->list);
			INIT_RCU_HEAD(&addr->rcu);
			list_add_tail(&addr->list, addrlist);
		}
	}
+39 −15
Original line number Diff line number Diff line
@@ -153,6 +153,9 @@ static void sctp_v4_copy_addrlist(struct list_head *addrlist,
			addr->a.v4.sin_family = AF_INET;
			addr->a.v4.sin_port = 0;
			addr->a.v4.sin_addr.s_addr = ifa->ifa_local;
			addr->valid = 1;
			INIT_LIST_HEAD(&addr->list);
			INIT_RCU_HEAD(&addr->rcu);
			list_add_tail(&addr->list, addrlist);
		}
	}
@@ -192,16 +195,24 @@ static void sctp_free_local_addr_list(void)
	}
}

void sctp_local_addr_free(struct rcu_head *head)
{
	struct sctp_sockaddr_entry *e = container_of(head,
				struct sctp_sockaddr_entry, rcu);
	kfree(e);
}

/* Copy the local addresses which are valid for 'scope' into 'bp'.  */
int sctp_copy_local_addr_list(struct sctp_bind_addr *bp, sctp_scope_t scope,
			      gfp_t gfp, int copy_flags)
{
	struct sctp_sockaddr_entry *addr;
	int error = 0;
	struct list_head *pos, *temp;

	list_for_each_safe(pos, temp, &sctp_local_addr_list) {
		addr = list_entry(pos, struct sctp_sockaddr_entry, list);
	rcu_read_lock();
	list_for_each_entry_rcu(addr, &sctp_local_addr_list, list) {
		if (!addr->valid)
			continue;
		if (sctp_in_scope(&addr->a, scope)) {
			/* Now that the address is in scope, check to see if
			 * the address type is really supported by the local
@@ -221,6 +232,7 @@ int sctp_copy_local_addr_list(struct sctp_bind_addr *bp, sctp_scope_t scope,
	}

end_copy:
	rcu_read_unlock();
	return error;
}

@@ -600,13 +612,18 @@ static void sctp_v4_seq_dump_addr(struct seq_file *seq, union sctp_addr *addr)
	seq_printf(seq, "%d.%d.%d.%d ", NIPQUAD(addr->v4.sin_addr));
}

/* Event handler for inet address addition/deletion events.  */
/* Event handler for inet address addition/deletion events.
 * The sctp_local_addr_list needs to be protocted by a spin lock since
 * multiple notifiers (say IPv4 and IPv6) may be running at the same
 * time and thus corrupt the list.
 * The reader side is protected with RCU.
 */
static int sctp_inetaddr_event(struct notifier_block *this, unsigned long ev,
			       void *ptr)
{
	struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
	struct sctp_sockaddr_entry *addr;
	struct list_head *pos, *temp;
	struct sctp_sockaddr_entry *addr = NULL;
	struct sctp_sockaddr_entry *temp;

	switch (ev) {
	case NETDEV_UP:
@@ -615,19 +632,25 @@ static int sctp_inetaddr_event(struct notifier_block *this, unsigned long ev,
			addr->a.v4.sin_family = AF_INET;
			addr->a.v4.sin_port = 0;
			addr->a.v4.sin_addr.s_addr = ifa->ifa_local;
			list_add_tail(&addr->list, &sctp_local_addr_list);
			addr->valid = 1;
			spin_lock_bh(&sctp_local_addr_lock);
			list_add_tail_rcu(&addr->list, &sctp_local_addr_list);
			spin_unlock_bh(&sctp_local_addr_lock);
		}
		break;
	case NETDEV_DOWN:
		list_for_each_safe(pos, temp, &sctp_local_addr_list) {
			addr = list_entry(pos, struct sctp_sockaddr_entry, list);
		spin_lock_bh(&sctp_local_addr_lock);
		list_for_each_entry_safe(addr, temp,
					&sctp_local_addr_list, list) {
			if (addr->a.v4.sin_addr.s_addr == ifa->ifa_local) {
				list_del(pos);
				kfree(addr);
				addr->valid = 0;
				list_del_rcu(&addr->list);
				break;
			}
		}

		spin_unlock_bh(&sctp_local_addr_lock);
		if (addr && !addr->valid)
			call_rcu(&addr->rcu, sctp_local_addr_free);
		break;
	}

@@ -1160,6 +1183,7 @@ SCTP_STATIC __init int sctp_init(void)

	/* Initialize the local address list. */
	INIT_LIST_HEAD(&sctp_local_addr_list);
	spin_lock_init(&sctp_local_addr_lock);
	sctp_get_local_addr_list();

	/* Register notifier for inet address additions/deletions. */
@@ -1227,6 +1251,9 @@ SCTP_STATIC __exit void sctp_exit(void)
	sctp_v6_del_protocol();
	inet_del_protocol(&sctp_protocol, IPPROTO_SCTP);

	/* Unregister notifier for inet address additions/deletions. */
	unregister_inetaddr_notifier(&sctp_inetaddr_notifier);

	/* Free the local address list.  */
	sctp_free_local_addr_list();

@@ -1240,9 +1267,6 @@ SCTP_STATIC __exit void sctp_exit(void)
	inet_unregister_protosw(&sctp_stream_protosw);
	inet_unregister_protosw(&sctp_seqpacket_protosw);

	/* Unregister notifier for inet address additions/deletions. */
	unregister_inetaddr_notifier(&sctp_inetaddr_notifier);

	sctp_sysctl_unregister();
	list_del(&sctp_ipv4_specific.list);

Loading