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

Commit 5afbff8d authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "netfilter: Fix for SIP crashes when handling Segmented messages"

parents d196654e 0f27a4ff
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