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

Commit 0f27a4ff authored by Ravinder Konka's avatar Ravinder Konka
Browse files

netfilter: Fix for SIP crashes when handling Segmented messages



Added handling to handle SIP chat messages of big length and also
handle special cases where second SIP fragment is cascaded with
other SIP control message.

Change-Id: I4b1895460b6dae21e0080e126408ba0ce72539f5
Signed-off-by: default avatarRavinder Konka <rkonka@codeaurora.org>
parent 422bb915
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ struct sip_length {
	int msg_length[SIP_LIST_ELEMENTS];
	int skb_len[SIP_LIST_ELEMENTS];
	int data_len[SIP_LIST_ELEMENTS];
	int orig_data_len[SIP_LIST_ELEMENTS];
};

/* per conntrack: protocol private data */
@@ -132,6 +133,7 @@ struct nf_conn {

	struct list_head sip_segment_list;
	const char *dptr_prev;
	bool dir_prev;
	struct sip_length segment;
	bool sip_original_dir;
	bool sip_reply_dir;
+150 −18
Original line number Diff line number Diff line
@@ -60,6 +60,8 @@ static unsigned nf_ct_disable_sip_alg;
static int sip_direct_media = 1;
static unsigned nf_ct_enable_sip_segmentation;
static int packet_count;
static bool split_skb;

static
int proc_sip_segment(struct ctl_table *ctl, int write,
		     void __user *buffer, size_t *lenp, loff_t *ppos);
@@ -128,7 +130,7 @@ static void sip_update_params(enum ip_conntrack_dir dir,
 */
static bool sip_save_segment_info(struct nf_conn *ct, struct sk_buff *skb,
				  unsigned int msglen, unsigned int datalen,
				  const char *dptr,
				  unsigned int datalen_orig, const char *dptr,
				  enum ip_conntrack_info ctinfo)
{
	enum ip_conntrack_dir dir = IP_CT_DIR_MAX;
@@ -151,7 +153,9 @@ static bool sip_save_segment_info(struct nf_conn *ct, struct sk_buff *skb,
			ct->segment.msg_length[0] = msglen;
			ct->segment.data_len[0] = datalen;
			ct->segment.skb_len[0] = skb->len;
			ct->segment.orig_data_len[0] = datalen_orig;
			ct->dptr_prev = dptr;
			ct->dir_prev = dir;
			ct->sip_original_dir = 1;
			skip = false;
		}
@@ -162,7 +166,9 @@ static bool sip_save_segment_info(struct nf_conn *ct, struct sk_buff *skb,
			ct->segment.msg_length[1] = msglen;
			ct->segment.data_len[1] = datalen;
			ct->segment.skb_len[1] = skb->len;
			ct->segment.orig_data_len[1] = datalen_orig;
			ct->dptr_prev = dptr;
			ct->dir_prev = dir;
			ct->sip_reply_dir = 1;
			skip = false;
		}
@@ -170,6 +176,66 @@ static bool sip_save_segment_info(struct nf_conn *ct, struct sk_buff *skb,
	return skip;
}

static bool sip_calculate_sequence_num(struct nf_conn *ct,
				       struct sk_buff **skb_ref,
				       enum ip_conntrack_info ctinfo)
{
	struct list_head *list_trav_node;
	struct list_head *list_backup_node;
	struct nf_conn *ct_list;
	enum ip_conntrack_info ctinfo_list;
	enum ip_conntrack_dir dir_list;
	enum ip_conntrack_dir dir = IP_CT_DIR_MAX;
	const struct tcphdr *th_old;
	unsigned int prev_data_len;
	unsigned int seq_no, seq_old, exp_seq_no;
	const struct tcphdr *th_new;
	struct sip_list *sip_entry = NULL;
	int orig_data_len = 0;
	int reply_data_len = 0;

	th_new = (struct tcphdr *)(skb_network_header(*skb_ref) +
		ip_hdrlen(*skb_ref));
	seq_no = ntohl(th_new->seq);
	dir = CTINFO2DIR(ctinfo);
	/* traverse the list it would have 1 or 2 elements. 1 element per
	 * direction at max
	 */
	list_for_each_safe(list_trav_node, list_backup_node,
			   &ct->sip_segment_list){
		sip_entry = list_entry(list_trav_node, struct sip_list, list);
		ct_list = nf_ct_get(sip_entry->entry->skb, &ctinfo_list);
		dir_list = CTINFO2DIR(ctinfo_list);
		/* take an element and check if its direction matches with the
		 * current one
		 */
		if (dir_list == dir) {
			/* once we have the two elements to be combined we do
			 * another check. match the next expected seq no of the
			 * packet in the list with the seq no of the current
			 * packet.this is to be protected  against out of order
			 * fragments
			 */
			th_old = ((struct tcphdr *)(skb_network_header
				(sip_entry->entry->skb) +
				ip_hdrlen(sip_entry->entry->skb)));
			orig_data_len = ct->segment.orig_data_len[0];
			reply_data_len = ct->segment.orig_data_len[1];

			prev_data_len = (dir == IP_CT_DIR_ORIGINAL) ?
			 orig_data_len : reply_data_len;
			seq_old = (ntohl(th_old->seq));
			exp_seq_no = seq_old+prev_data_len;

			if (exp_seq_no == seq_no)
				return true;
			else
				return false;
		}
	}
	return false;
}

static struct sip_list *sip_coalesce_segments(struct nf_conn *ct,
					      struct sk_buff **skb_ref,
					      unsigned int dataoff,
@@ -193,6 +259,8 @@ static struct sip_list *sip_coalesce_segments(struct nf_conn *ct,
	bool fragstolen = false;
	int delta_truesize = 0;
	struct sip_list *sip_entry = NULL;
	int orig_data_len = 0;
	int reply_data_len = 0;

	th_new = (struct tcphdr *)(skb_network_header(*skb_ref) +
		ip_hdrlen(*skb_ref));
@@ -219,9 +287,11 @@ static struct sip_list *sip_coalesce_segments(struct nf_conn *ct,
			th_old = ((struct tcphdr *)(skb_network_header
				(sip_entry->entry->skb) +
				ip_hdrlen(sip_entry->entry->skb)));
			orig_data_len = ct->segment.orig_data_len[0];
			reply_data_len = ct->segment.orig_data_len[1];

			prev_data_len = (dir == IP_CT_DIR_ORIGINAL) ?
			 ct->segment.data_len[0] : ct->segment.data_len[1];
			 orig_data_len : reply_data_len;
			seq_old = (ntohl(th_old->seq));
			exp_seq_no = seq_old+prev_data_len;

@@ -1814,8 +1884,8 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff,
	s16 diff, tdiff = 0;
	int ret = NF_ACCEPT;
	bool term;
	unsigned int datalen = 0, msglen = 0, origlen = 0;
	unsigned int dataoff_orig = 0;
	unsigned int datalen = 0, msglen = 0, origlen = 0, partialdatalen = 0;
	unsigned int dataoff_orig = 0, datalen_orig = 0;
	unsigned int splitlen, oldlen, oldlen1;
	struct sip_list *sip_entry = NULL;
	bool skip_sip_process = false;
@@ -1823,9 +1893,10 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff,
	bool skip = false;
	bool skb_is_combined = false;
	enum ip_conntrack_dir dir = IP_CT_DIR_MAX;
	struct sk_buff *combined_skb = NULL;
	struct sk_buff *combined_skb = NULL, *tskb = NULL;
	bool content_len_exists = 1;
	unsigned int len_skb = 0;
	struct sk_buff *nskb = NULL;

	packet_count++;
	pr_debug("packet count %d\n", packet_count);
@@ -1852,6 +1923,8 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff,

	dptr = skb->data + dataoff;
	datalen = skb->len - dataoff;
	datalen_orig = datalen;

	if (datalen < strlen("SIP/2.0 200"))
		return NF_ACCEPT;

@@ -1863,6 +1936,10 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff,

	if (!ct)
		return NF_DROP;

	dir = CTINFO2DIR(ctinfo);

sip_header_process:
	while (1) {
		if (ct_sip_get_header(ct, dptr, 0, datalen,
				      SIP_HDR_CONTENT_LENGTH,
@@ -1875,7 +1952,24 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff,
				break;
			}
		}

		/* If the below condition is true it indicates packet
		 * is already present in Queue and we ve received a
		 * packet with content length field present which needs
		 * special handling.
		 */
		if ((dir == ct->dir_prev) &&
		    (ct->sip_original_dir || ct->sip_reply_dir) &&
		    sip_calculate_sequence_num(ct, &skb, ctinfo)) {
				/* Make a Copy of the skb for further
				 * use and make sure to free it
				 */
				nskb = skb_copy(skb, GFP_ATOMIC);
				if (!nskb) {
					pr_err("Cannot create SKB copy\n");
					return NF_DROP;
				}
				split_skb = true;
		}
		clen = simple_strtoul(dptr + matchoff, (char **)&end, 10);
		if (dptr + matchoff == end) {
			break;
@@ -1902,13 +1996,17 @@ destination:
			msglen = origlen;
		}
		pr_debug("mslgen %d datalen %d\n", msglen, datalen);
		if (!strncasecmp(dptr, "MESSAGE", strlen("MESSAGE")))
			return NF_ACCEPT;
		dir = CTINFO2DIR(ctinfo);
		combined_skb = skb;
		if (nf_ct_enable_sip_segmentation) {
			/* Segmented Packet */
			if (msglen > datalen) {
				skip = sip_save_segment_info(ct, skb, msglen,
							     datalen, dptr,
							     datalen,
							     datalen_orig,
							     dptr,
							     ctinfo);
				if (!skip)
					return NF_QUEUE;
@@ -1917,16 +2015,32 @@ destination:
			/*Traverse the list if list non empty */
			if (((&ct->sip_segment_list)->next) !=
				(&ct->sip_segment_list)) {
				/* Combine segments if they are fragments of
				 *  the same message.
				/* Combine segments if they are
				 * fragments of the same message.
				 */
				tskb = skb;

				if (split_skb) {
					/* Adjust the second skb length
					 * and datalength to have only the
					 * trailing part of the fragmented
					 * SIP message
					 */
				sip_entry = sip_coalesce_segments(ct, &skb,
					partialdatalen =
					ct->segment.msg_length[dir] -
					ct->segment.data_len[dir];
					skb_trim(nskb, dataoff+partialdatalen);
					tskb = nskb;
				}

				sip_entry = sip_coalesce_segments(ct, &tskb,
								  dataoff,
								  &combined_skb,
							 &skip_sip_process,
							 do_not_process,
								  ctinfo,
							 &skb_is_combined);

				sip_update_params(dir, &msglen, &origlen, &dptr,
						  &datalen,
						  skb_is_combined, ct);
@@ -1966,10 +2080,17 @@ destination:
		}
		len_skb = combined_skb->len - splitlen;
		pr_debug("len to copy is %d\n", len_skb);

		if (!split_skb)
			skb_copy_from_linear_data_offset(combined_skb,
							 splitlen, skb->data,
							 len_skb);
		skb_split(combined_skb, skb, splitlen);

		/* Update the first SIP segment details */
		combined_skb->data_len = 0;
		combined_skb->len = splitlen;
		skb_set_tail_pointer(combined_skb, splitlen);

		/* Headers need to be recalculated since during SIP processing
		 * headers are calculated based on the change in length of the
		 * combined message
@@ -1980,6 +2101,17 @@ destination:
			nf_reinject(sip_entry->entry, NF_ACCEPT);
			kfree(sip_entry);
		}

		if (split_skb) {
			if (dir == IP_CT_DIR_ORIGINAL)
				ct->sip_original_dir = 0;
			else
				ct->sip_reply_dir = 0;
			kfree_skb(nskb);
			split_skb =  false;
			goto sip_header_process;
		}

		skb->len = (oldlen1 + protoff) + tdiff - dataoff_orig;
		/* After splitting, push the headers back to the first skb which
		 * were removed before combining the skbs.This moves the skb