summaryrefslogtreecommitdiff
path: root/net/batman-adv/fragmentation.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/batman-adv/fragmentation.c')
-rw-r--r--net/batman-adv/fragmentation.c125
1 files changed, 101 insertions, 24 deletions
diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
index f4e45cc25816..493af3091aa8 100644
--- a/net/batman-adv/fragmentation.c
+++ b/net/batman-adv/fragmentation.c
@@ -8,6 +8,7 @@
#include "main.h"
#include <linux/atomic.h>
+#include <linux/bug.h>
#include <linux/byteorder/generic.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
@@ -17,6 +18,7 @@
#include <linux/lockdep.h>
#include <linux/minmax.h>
#include <linux/netdevice.h>
+#include <linux/overflow.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
@@ -80,9 +82,9 @@ void batadv_frag_purge_orig(struct batadv_orig_node *orig_node,
*
* Return: the maximum size of payload that can be fragmented.
*/
-static int batadv_frag_size_limit(void)
+static size_t batadv_frag_size_limit(void)
{
- int limit = BATADV_FRAG_MAX_FRAG_SIZE;
+ size_t limit = BATADV_FRAG_MAX_FRAG_SIZE;
limit -= sizeof(struct batadv_frag_packet);
limit *= BATADV_FRAG_MAX_FRAGMENTS;
@@ -137,13 +139,17 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node,
struct sk_buff *skb,
struct hlist_head *chain_out)
{
- struct batadv_frag_table_entry *chain;
- struct batadv_frag_list_entry *frag_entry_new = NULL, *frag_entry_curr;
struct batadv_frag_list_entry *frag_entry_last = NULL;
+ struct batadv_frag_list_entry *frag_entry_new = NULL;
+ u16 hdr_size = sizeof(struct batadv_frag_packet);
+ struct batadv_frag_list_entry *frag_entry_curr;
struct batadv_frag_packet *frag_packet;
- u8 bucket;
- u16 seqno, hdr_size = sizeof(struct batadv_frag_packet);
+ struct batadv_frag_table_entry *chain;
+ bool overflow = false;
bool ret = false;
+ size_t data_len;
+ u8 bucket;
+ u16 seqno;
/* Linearize packet to avoid linearizing 16 packets in a row when doing
* the later merge. Non-linear merge should be added to remove this
@@ -153,6 +159,7 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node,
goto err;
frag_packet = (struct batadv_frag_packet *)skb->data;
+ data_len = skb->len - hdr_size;
seqno = ntohs(frag_packet->seqno);
bucket = seqno % BATADV_FRAG_BUFFER_COUNT;
@@ -171,7 +178,7 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node,
spin_lock_bh(&chain->lock);
if (batadv_frag_init_chain(chain, seqno)) {
hlist_add_head(&frag_entry_new->list, &chain->fragment_list);
- chain->size = skb->len - hdr_size;
+ chain->size = data_len;
chain->timestamp = jiffies;
chain->total_size = ntohs(frag_packet->total_size);
ret = true;
@@ -188,7 +195,11 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node,
if (frag_entry_curr->no < frag_entry_new->no) {
hlist_add_before(&frag_entry_new->list,
&frag_entry_curr->list);
- chain->size += skb->len - hdr_size;
+
+ if (check_add_overflow(chain->size, data_len,
+ &chain->size))
+ overflow = true;
+
chain->timestamp = jiffies;
ret = true;
goto out;
@@ -201,13 +212,16 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node,
/* Reached the end of the list, so insert after 'frag_entry_last'. */
if (likely(frag_entry_last)) {
hlist_add_behind(&frag_entry_new->list, &frag_entry_last->list);
- chain->size += skb->len - hdr_size;
+
+ if (check_add_overflow(chain->size, data_len, &chain->size))
+ overflow = true;
+
chain->timestamp = jiffies;
ret = true;
}
out:
- if (chain->size > batadv_frag_size_limit() ||
+ if (overflow || chain->size > batadv_frag_size_limit() ||
chain->total_size != ntohs(frag_packet->total_size) ||
chain->total_size > batadv_frag_size_limit()) {
/* Clear chain if total size of either the list or the packet
@@ -246,11 +260,12 @@ err:
static struct sk_buff *
batadv_frag_merge_packets(struct hlist_head *chain)
{
- struct batadv_frag_packet *packet;
+ int hdr_size = sizeof(struct batadv_frag_packet);
struct batadv_frag_list_entry *entry;
+ struct batadv_frag_packet *packet;
struct sk_buff *skb_out;
- int size, hdr_size = sizeof(struct batadv_frag_packet);
bool dropped = false;
+ int size;
/* Remove first entry, as this is the destination for the rest of the
* fragments.
@@ -294,6 +309,32 @@ free:
}
/**
+ * batadv_skb_is_frag() - check if newly merged skb contains unicast fragment
+ * @skb: newly merged skb
+ *
+ * Return: true if the newly merged skb is of type BATADV_UNICAST_FRAG, false
+ * otherwise
+ */
+static bool batadv_skb_is_frag(struct sk_buff *skb)
+{
+ struct batadv_ogm_packet *batadv_ogm_packet;
+
+ /* packet should hold at least type and version */
+ if (unlikely(!pskb_may_pull(skb, 2)))
+ return false;
+
+ batadv_ogm_packet = (struct batadv_ogm_packet *)skb->data;
+
+ if (batadv_ogm_packet->version != BATADV_COMPAT_VERSION)
+ return false;
+
+ if (batadv_ogm_packet->packet_type != BATADV_UNICAST_FRAG)
+ return false;
+
+ return true;
+}
+
+/**
* batadv_frag_skb_buffer() - buffer fragment for later merge
* @skb: skb to buffer
* @orig_node_src: originator that the skb is received from
@@ -310,8 +351,8 @@ free:
bool batadv_frag_skb_buffer(struct sk_buff **skb,
struct batadv_orig_node *orig_node_src)
{
- struct sk_buff *skb_out = NULL;
struct hlist_head head = HLIST_HEAD_INIT;
+ struct sk_buff *skb_out = NULL;
bool ret = false;
/* Add packet to buffer and table entry if merge is possible. */
@@ -326,6 +367,16 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
if (!skb_out)
goto out_err;
+ /* fragment in fragment is not allowed. otherwise it is possible
+ * to exhaust the stack when receiving a matryoshka-style
+ * "fragments in a fragment packet"
+ */
+ if (batadv_skb_is_frag(skb_out)) {
+ kfree_skb(skb_out);
+ skb_out = NULL;
+ goto out_err;
+ }
+
out:
ret = true;
out_err:
@@ -338,6 +389,8 @@ out_err:
* @skb: skb to forward
* @recv_if: interface that the skb is received on
* @orig_node_src: originator that the skb is received from
+ * @rx_result: set to NET_RX_SUCCESS when the fragment was forwarded and
+ * NET_RX_DROP when it was dropped; only valid when true is returned
*
* Look up the next-hop of the fragments payload and check if the merged packet
* will exceed the MTU towards the next-hop. If so, the fragment is forwarded
@@ -347,13 +400,14 @@ out_err:
*/
bool batadv_frag_skb_fwd(struct sk_buff *skb,
struct batadv_hard_iface *recv_if,
- struct batadv_orig_node *orig_node_src)
+ struct batadv_orig_node *orig_node_src,
+ int *rx_result)
{
struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface);
struct batadv_neigh_node *neigh_node = NULL;
struct batadv_frag_packet *packet;
- u16 total_size;
bool ret = false;
+ u16 total_size;
packet = (struct batadv_frag_packet *)skb->data;
@@ -366,12 +420,29 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
*/
total_size = ntohs(packet->total_size);
if (total_size > neigh_node->if_incoming->net_dev->mtu) {
+ if (packet->ttl < 2) {
+ kfree_skb(skb);
+ *rx_result = NET_RX_DROP;
+ ret = true;
+ goto out;
+ }
+
+ if (skb_cow(skb, ETH_HLEN) < 0) {
+ kfree_skb(skb);
+ *rx_result = NET_RX_DROP;
+ ret = true;
+ goto out;
+ }
+
+ packet = (struct batadv_frag_packet *)skb->data;
+
batadv_inc_counter(bat_priv, BATADV_CNT_FRAG_FWD);
batadv_add_counter(bat_priv, BATADV_CNT_FRAG_FWD_BYTES,
skb->len + ETH_HLEN);
packet->ttl--;
batadv_send_unicast_skb(skb, neigh_node);
+ *rx_result = NET_RX_SUCCESS;
ret = true;
}
@@ -400,10 +471,11 @@ static struct sk_buff *batadv_frag_create(struct net_device *net_dev,
{
unsigned int ll_reserved = LL_RESERVED_SPACE(net_dev);
unsigned int tailroom = net_dev->needed_tailroom;
- struct sk_buff *skb_fragment;
unsigned int header_size = sizeof(*frag_head);
- unsigned int mtu = fragment_size + header_size;
+ struct sk_buff *skb_fragment;
+ unsigned int mtu;
+ mtu = fragment_size + header_size;
skb_fragment = dev_alloc_skb(ll_reserved + mtu + tailroom);
if (!skb_fragment)
goto err;
@@ -435,23 +507,28 @@ int batadv_frag_send_packet(struct sk_buff *skb,
struct batadv_neigh_node *neigh_node)
{
struct net_device *net_dev = neigh_node->if_incoming->net_dev;
- struct batadv_priv *bat_priv;
struct batadv_hard_iface *primary_if = NULL;
struct batadv_frag_packet frag_header;
- struct sk_buff *skb_fragment;
unsigned int mtu = net_dev->mtu;
- unsigned int header_size = sizeof(frag_header);
- unsigned int max_fragment_size, num_fragments;
+ unsigned int max_fragment_size;
+ struct batadv_priv *bat_priv;
+ struct sk_buff *skb_fragment;
+ unsigned int num_fragments;
+ unsigned int header_size;
int ret;
+ header_size = sizeof(frag_header);
+
/* To avoid merge and refragmentation at next-hops we never send
* fragments larger than BATADV_FRAG_MAX_FRAG_SIZE
*/
mtu = min_t(unsigned int, mtu, BATADV_FRAG_MAX_FRAG_SIZE);
max_fragment_size = mtu - header_size;
- if (skb->len == 0 || max_fragment_size == 0)
- return -EINVAL;
+ if (skb->len == 0 || max_fragment_size == 0) {
+ ret = -EINVAL;
+ goto free_skb;
+ }
num_fragments = (skb->len - 1) / max_fragment_size + 1;
max_fragment_size = (skb->len - 1) / num_fragments + 1;
@@ -477,7 +554,7 @@ int batadv_frag_send_packet(struct sk_buff *skb,
*/
if (skb_has_frag_list(skb) && __skb_linearize(skb)) {
ret = -ENOMEM;
- goto free_skb;
+ goto put_primary_if;
}
/* Create one header to be copied to all fragments */