lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <alpine.LNX.2.20.1506171727200.3515@localhost.localdomain>
Date:	Wed, 17 Jun 2015 17:41:49 +0200 (CEST)
From:	Enrico Mioso <mrkiko.rs@...il.com>
To:	netdev@...r.kernel.org
cc:	Oliver Neukum <oneukum@...e.com>
Subject: Re: [RFC PATCH] cdc_ncm: support moving the NDP part of the frame
 to the end of NCM package

Hello guys.
Sorry for the long delay / timing: but I arrived to the conclusion I had to shut up and inform myself and write somecode.
:D
Yes, you madeit!
So here are my findings and answers.


On Tue, 9 Jun 2015, Oliver Neukum wrote:

==Date: Tue, 9 Jun 2015 12:38:59
==From: Oliver Neukum <oneukum@...e.com>
==To: Enrico Mioso <mrkiko.rs@...il.com>
==Cc: netdev@...r.kernel.org
==Subject: Re: [RFC PATCH] cdc_ncm: support moving the NDP part of the frame to
==    the end of NCM package
==
==On Tue, 2015-06-09 at 09:46 +0200, Enrico Mioso wrote:
==
==> ==Not another parameter please. If the alternate frames are processed as
==> ==well as the current frames, all is well and we can generally produces
==> ==such frames. If not, we want a black list.
==> 
==> I would agree on this point: and I was exploring different alternatives also, 
==> such as having this option settable when invoking cdc_ncm_bind_common from
==> huawei_cdc_ncm for example. Unfortunately, I don't feel confident we could do 
==> that.
==
==Using a module parameter is simply wrong. A system can be connected to
==multiple devices (in turn). As a minimum the feature must be per device.
==

Sure.

==> First of all: this driver supports more devices than Huawei ones, so I feel we 
==> have chances to break them modifying the frame structure.
==
==In theory we must never break or impede compliant devices for
==uncompliant devices. Yet I doubt we can break any device doing what
==Windows does.
==Does it have a standard NCM driver that works with Huawei devices?
==
==> Even more complicated is the fact that huawei NCM devices are not easily 
==> distinguishable one another from the driver perspective. Some heuristics may be 
==> put in place probably, but I don't have access to old enough NCM devices to 
==> know best.
==> The Huawei vendor driver put NDPs at end of frame - and does so for all devices 
==> in my opinion, but I can't be really sure about that.
==
==Doesn't it have a list of supported devices?

I don't have a Windows box readily at disposal to look into - partly due to 
accessiblity problems here. Still, Mobile Partner comes with it's own drivers 
for NCM devices.

==
==> Not now. Anyway I can change this as desired. Would something like a sysfs knob 
==> be preferable? User-space surely has a good chance to tell us what to do here.
==
==Not really. The answer will come from a list. The question is just
==whether easier updates are worth splitting up the fix.
==

The Linux huawei vendor driver defines various kinds of NDIS interfaces: but 
doesn't threat them differently when it comes to NCM framing: it always writes 
the NDP after the datagrams sequence, at least from what I understand reading 
the code.
Some device work with both the cdc_ncm-way and huawei_cdc-way, others don't.

==> ==> --- a/include/linux/usb/cdc_ncm.h
==> ==> +++ b/include/linux/usb/cdc_ncm.h
==> ==> @@ -93,6 +93,7 @@ struct cdc_ncm_ctx {
==> ==>  	const struct usb_cdc_mbim_desc *mbim_desc;
==> ==>  	const struct usb_cdc_mbim_extended_desc *mbim_extended_desc;
==> ==>  	const struct usb_cdc_ether_desc *ether_desc;
==> ==> +	struct usb_cdc_ncm_ndp16 *delayed_ndp;
==> ==
==> ==What prevents you from embedding this in the structure?
==> ==
==> ==	Regards
==> ==		Oliver
==> ==
==> ==
==> ==
==> Sorry - I think I didn't understand your ocmment here. Still, I am open to any 
==> suggestion.
==
==Why don't you put "struct usb_cdc_ncm_ndp16 delayed_ndp" as opposed to a
==pointer to it into struct cdc_ncm_ctx. You never need more than one at a
==time.
==
==	Regards
==		Oliver
==
==
==

I guess this will be clear reading the code I am posting here.

Based on the situation and constrains, I decided to re-write the NCM tx stack: 
and write a huawei specific one, in the huawei_cdc_ncm.c driver.
I can actually produce SKBs which are processed and seemigly received from the 
Linux cdc_ncm rx_fixup function, still not by the device I am testing this on, 
an E3131 3G dongle.
I don't know exactly why at the moment - still, I imagine two possibilities.
I might be aligning things wrong, or I might be doing some programming errors 
and corrupting the data somewhere. Any suggestion on how to verify this are 
welcome.

Si I post here the entire huawei_cdc_ncm.c file, since a patch would be 
probably not so smaller.
The only things I changed are still huawei_ncm_mgmt, huawei_ncm_tx_fixup and 
some defines got added.

I decided to split the NCM engine in two parts:
1 - The manager: which performs operations on frames based on what you ask.
2 - The packet processing path, which is in huawei_cdc_ncm_tx_fixup.

I expect all kinds of errors here: still hoping my work can show a serious 
intention in trying to do my best.
Obviously, printks sparkled all-over, huawei_show_ndp and probably many checks 
will go away. Some of them are redundant probably and make the code ugly.
I put them in place at least so I can avoid rebooting my virtual machine :D .

thank you,
Enrico

/* huawei_cdc_ncm.c - handles Huawei devices using the CDC NCM protocol as
 * transport layer.
 * Copyright (C) 2013	 Enrico Mioso <mrkiko.rs@...il.com>
 *
 *
 * ABSTRACT:
 * This driver handles devices resembling the CDC NCM standard, but
 * encapsulating another protocol inside it. An example are some Huawei 3G
 * devices, exposing an embedded AT channel where you can set up the NCM
 * connection.
 * This code has been heavily inspired by the cdc_mbim.c driver, which is
 * Copyright (c) 2012  Smith Micro Software, Inc.
 * Copyright (c) 2012  Bjørn Mork <bjorn@...k.no>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/ethtool.h>
#include <linux/if_vlan.h>
#include <linux/ip.h>
#include <linux/mii.h>
#include <linux/usb.h>
#include <linux/usb/cdc.h>
#include <linux/usb/usbnet.h>
#include <linux/usb/cdc-wdm.h>
#include <linux/usb/cdc_ncm.h>

/* NCM management operations: */

/* INIT_NCM_FRAME: prepare for a new frame.
 * NTH16 header is written to output SKB, and NDP data is reset.
 * Now, data may be added to this NCM package.
 */
#define NCM_INIT_FRAME		1

/* NCM_UPDATE_NDP: adds data to an NDP structure, hence updating it.
 * Some checks are performed to be sure data fits in, respecting device and
 * spec constrains.
 */
#define NCM_UPDATE_NDP		2

/* Write NDP: commits NDP to output SKB. */
#define NCM_COMMIT_NDP		3

/* NCM_FRAME_AVAILSPC: return to caller how much space is available in the frame.
 */
#define NCM_NDP_AVAILSPC	4

/* Driver data */
struct huawei_cdc_ncm_state {
	struct cdc_ncm_ctx *ctx;
	atomic_t pmcount;
	struct usb_driver *subdriver;
	struct usb_interface *control;
	struct usb_interface *data;

	/* Keeps track of where data starts and ends in SKBs. */
	int data_start;
	int data_len;

	/* Temporary NDP storage */
	struct usb_cdc_ncm_ndp16 *ndp;

	/* Temporary SKB for excess data. */
	struct sk_buff *skb_excess;
};

static int huawei_cdc_ncm_manage_power(struct usbnet *usbnet_dev, int on)
{
	struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
	int rv;

	if ((on && atomic_add_return(1, &drvstate->pmcount) == 1) ||
			(!on && atomic_dec_and_test(&drvstate->pmcount))) {
		rv = usb_autopm_get_interface(usbnet_dev->intf);
		usbnet_dev->intf->needs_remote_wakeup = on;
		if (!rv)
			usb_autopm_put_interface(usbnet_dev->intf);
	}
	return 0;
}

static void huawei_show_ndp(struct usb_cdc_ncm_ndp16 *ndp16) {
	u16 ndplen;
	int i;
	int index;

	if (!ndp16) {
		printk("\nNo NDP to show.");
		return;
	}

	ndplen = le16_to_cpu(ndp16->wLength);
	index = (ndplen - sizeof(struct usb_cdc_ncm_ndp16)) / sizeof(struct usb_cdc_ncm_dpe16) - 1;

	printk("\nNDP INFO: \nNDP length: %d; Points to next NDP at %d.",ndp16->wLength,ndp16->wNextNdpIndex);
	printk("\nDPE data follows: ");

	for (i=0;i<=index;i++) {
		printk("\nIndex %d: ",i);
		printk("datagram index %d, wdatagramlength %d.",cpu_to_le16(ndp16->dpe16[i].wDatagramIndex),cpu_to_le16(ndp16->dpe16[i].wDatagramLength));
	}
	printk("\nEND NDP INFO");
	return;
}

int huawei_ncm_mgmt(struct huawei_cdc_ncm_state *drvstate, struct sk_buff *skb_out, int mode) {
	struct usb_cdc_ncm_nth16 *nth16 = (struct usb_cdc_ncm_nth16 *)skb_out->data;
	struct cdc_ncm_ctx *ctx = drvstate->ctx;
	struct usb_cdc_ncm_ndp16 *ndp16 = drvstate->ndp;
	int ret = -EINVAL;
	u16 ndplen, index;

	switch(mode) {
		case NCM_INIT_FRAME:

			/* Write a new NTH16 header */
			nth16 = (struct usb_cdc_ncm_nth16 *)memset(skb_put(skb_out, sizeof(struct usb_cdc_ncm_nth16)), 0, sizeof(struct usb_cdc_ncm_nth16));
			if (!nth16) {
				printk("\nNo memory for NTH.");
				ret = -EINVAL;
				goto error;
			}

			/* NTH16 signature and header length are known a-priori. */
			nth16->dwSignature = cpu_to_le32(USB_CDC_NCM_NTH16_SIGN);
			nth16->wHeaderLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_nth16));
			
			/* TX sequence numbering: not initialized. */
			nth16->wSequence = cpu_to_le16(ctx->tx_seq++);

			/* Prepare a new NDP to add data on subsequent calls. */
			ndp16 = memset(drvstate->ndp, 0, ctx->max_ndp_size);

			/* Initial NDP length */
			ndp16->wLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_ndp16) + sizeof(struct usb_cdc_ncm_dpe16));

			/* Frame signature: to be reworked. */
			ndp16->dwSignature = cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN);

			/* All went well. */
			ret = 0;
			break;

		case NCM_UPDATE_NDP:

			/* Do we have enough space for the data? */
			if (skb_out->len + ctx->max_ndp_size > ctx->tx_max) {
				printk("\nDevice constrains are not allowing this data.");
				return ret;
			}

			/* Calculate frame number withing this NDP */
			ndplen = le16_to_cpu(ndp16->wLength);
			index = (ndplen - sizeof(struct usb_cdc_ncm_ndp16)) / sizeof(struct usb_cdc_ncm_dpe16) - 1;

			if (index >= CDC_NCM_DPT_DATAGRAMS_MAX) {
				printk("\nDPT16 limit.");
				return ret;
			}

			/* tx_max shouldn't be exceeded after committing. */
			if (ndplen + sizeof(struct usb_cdc_ncm_dpe16) > ctx->tx_max) {
				printk("\nNo room for NDP.");
				return ret;
			}

			/* Adding a DPT entry. */
			ndp16->dpe16[index].wDatagramLength = cpu_to_le16(drvstate->data_len);
			ndp16->dpe16[index].wDatagramIndex = cpu_to_le16(drvstate->data_start);
			ndp16->wLength = cpu_to_le16(ndplen + sizeof(struct usb_cdc_ncm_dpe16));

			ret = 0;
			break;

		case NCM_COMMIT_NDP:

			/* Add the NULL DPE
			ndplen = le16_to_cpu(ndp16->wLength);
			index = (ndplen - sizeof(struct usb_cdc_ncm_ndp16)) / sizeof(struct usb_cdc_ncm_dpe16) - 1;
			ndp16->wLength = cpu_to_le16(ndplen + sizeof(struct usb_cdc_ncm_dpe16)); */

			/* NTH is here! */
			nth16->wNdpIndex = cpu_to_le16(skb_out->len);

			/* Write NDP */
			memcpy(skb_put(skb_out, ctx->max_ndp_size), ndp16, ctx->max_ndp_size);

			/* Finalize NTH16 header, setting it's block length */
			nth16->wBlockLength = cpu_to_le16(skb_out->len);

			huawei_show_ndp(ndp16);
			ret = 0;
			break;
		default:
			ret = -EINVAL;
			printk("\nInvalid operation requested: %d.",mode);
			break;
	}

error:
	return ret;
}

struct sk_buff *
huawei_ncm_tx_fixup(struct usbnet *dev, struct sk_buff *skb_in, gfp_t flags) {
	struct huawei_cdc_ncm_state *drvstate = (void *)&dev->data;
	struct cdc_ncm_ctx *ctx = drvstate->ctx;
	struct sk_buff *skb_out;
	int status;

	/* Allocate a fresh OUT skb. */
	skb_out = alloc_skb(ctx->tx_max, GFP_NOIO);
	printk("\nSKB allocated.");

	/* Create base NCM structure */
	status = huawei_ncm_mgmt(drvstate, skb_out, NCM_INIT_FRAME);
	printk("\nStarting new NCM frame.");

	/* Do we have excess data? */
	if (drvstate->skb_excess) {
		drvstate->data_start = skb_out->len;

		/* Align beginning of next frame. */
		cdc_ncm_align_tail(skb_out,  ctx->tx_modulus, ctx->tx_remainder, ctx->tx_max);

		memcpy(skb_put(skb_out, drvstate->skb_excess->len), drvstate->skb_excess->data, drvstate->skb_excess->len);
		drvstate->data_len = skb_out->len-drvstate->data_start;

		printk("\nCopying excess data: from %d to %d, asking mgr to update NDP.",drvstate->data_start,skb_out->len);
		status = huawei_ncm_mgmt(drvstate, skb_out, NCM_UPDATE_NDP);
		if (status) {
			printk("\nExcess data can't fit in NDP. MTU problem? ");
			goto error_dealloc;
		}

		dev_kfree_skb_any(drvstate->skb_excess);
	}

	/* Timer context may be placed here. */

	if (skb_in->len + skb_out->len > ctx->tx_max) {
		printk("\nTotal data can not fit, still excess can be sent.");
		/* Still we might try to send out excess data we already added. */
		if (skb_out->len != 0) {
			goto send_out;
		}

		goto error_dealloc;
	}

	/* Align beginning of next frame. */
	cdc_ncm_align_tail(skb_out,  ctx->tx_modulus, ctx->tx_remainder, ctx->tx_max);

	drvstate->data_start = skb_out->len;
	memcpy(skb_put(skb_out, skb_in->len), skb_in->data, skb_in->len);
	drvstate->data_len = skb_out->len-drvstate->data_start;
	printk("\nIN data copied from %d to %d, asking MGR to update NDP.",drvstate->data_start,skb_out->len);

	/* Add data to NDP */
	status = huawei_ncm_mgmt(drvstate, skb_out, NCM_UPDATE_NDP);
	if (status) {
		printk("\nNew data can not fit in NDP.");

		drvstate->skb_excess = alloc_skb(skb_in->len, GFP_NOIO);
		if (!drvstate->skb_excess) {
			printk("\nCan not allocate excess data buffer.");
			goto error_dealloc;
		}
		memcpy(skb_put(drvstate->skb_excess, skb_in->len), skb_in->data, skb_in->len);
		goto send_out;
	}

send_out:
	/* Write final NDP frame */
	printk("\nCommitting NDP.");

	/* Align new NDP */
	cdc_ncm_align_tail(skb_out, ctx->tx_ndp_modulus, 0, ctx->tx_max);

	status = huawei_ncm_mgmt(drvstate, skb_out, NCM_COMMIT_NDP);
	if (status) {
		printk("\nError writing final NDP.");
		goto error_dealloc;
	}

	if (skb_in) {
		dev_kfree_skb_any(skb_in);
		printk("\nIN skb deallocated.");
	}

	//cdc_ncm_rx_test(dev, skb_out);
	return skb_out;
error_dealloc:
	if (drvstate->skb_excess)
		dev_kfree_skb_any(drvstate->skb_excess);
//not_enough_data:
	if (skb_in)
		dev_kfree_skb_any(skb_in);
	return NULL;
}

static int huawei_cdc_ncm_wdm_manage_power(struct usb_interface *intf,
					   int status)
{
	struct usbnet *usbnet_dev = usb_get_intfdata(intf);

	/* can be called while disconnecting */
	if (!usbnet_dev)
		return 0;

	return huawei_cdc_ncm_manage_power(usbnet_dev, status);
}


static int huawei_cdc_ncm_bind(struct usbnet *usbnet_dev,
			       struct usb_interface *intf)
{
	struct cdc_ncm_ctx *ctx;
	struct usb_driver *subdriver = ERR_PTR(-ENODEV);
	int ret = -ENODEV;
	struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;

	/* altsetting should always be 1 for NCM devices - so we hard-coded
	 * it here
	 */
	ret = cdc_ncm_bind_common(usbnet_dev, intf, 1);
	if (ret)
		goto err;

	ctx = drvstate->ctx;

	if (usbnet_dev->status)
		/* The wMaxCommand buffer must be big enough to hold
		 * any message from the modem. Experience has shown
		 * that some replies are more than 256 bytes long
		 */
		subdriver = usb_cdc_wdm_register(ctx->control,
						 &usbnet_dev->status->desc,
						 1024, /* wMaxCommand */
						 huawei_cdc_ncm_wdm_manage_power);
	if (IS_ERR(subdriver)) {
		ret = PTR_ERR(subdriver);
		cdc_ncm_unbind(usbnet_dev, intf);
		goto err;
	}

	/* Prevent usbnet from using the status descriptor */
	usbnet_dev->status = NULL;

	drvstate->subdriver = subdriver;

	/* Allocate temporary NDP storage for NDP building */
	drvstate->ndp = kzalloc(ctx->max_ndp_size, GFP_KERNEL);

err:
	return ret;
}

static void huawei_cdc_ncm_unbind(struct usbnet *usbnet_dev,
				  struct usb_interface *intf)
{
	struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
	struct cdc_ncm_ctx *ctx = drvstate->ctx;

	if (drvstate->subdriver && drvstate->subdriver->disconnect)
		drvstate->subdriver->disconnect(ctx->control);
	drvstate->subdriver = NULL;

	cdc_ncm_unbind(usbnet_dev, intf);

	/* Deallocate temporary NDP storage */
	kfree(drvstate->ndp);
}

static int huawei_cdc_ncm_suspend(struct usb_interface *intf,
				  pm_message_t message)
{
	int ret = 0;
	struct usbnet *usbnet_dev = usb_get_intfdata(intf);
	struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
	struct cdc_ncm_ctx *ctx = drvstate->ctx;

	if (ctx == NULL) {
		ret = -ENODEV;
		goto error;
	}

	ret = usbnet_suspend(intf, message);
	if (ret < 0)
		goto error;

	if (intf == ctx->control &&
		drvstate->subdriver &&
		drvstate->subdriver->suspend)
		ret = drvstate->subdriver->suspend(intf, message);
	if (ret < 0)
		usbnet_resume(intf);

error:
	return ret;
}

static int huawei_cdc_ncm_resume(struct usb_interface *intf)
{
	int ret = 0;
	struct usbnet *usbnet_dev = usb_get_intfdata(intf);
	struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
	bool callsub;
	struct cdc_ncm_ctx *ctx = drvstate->ctx;

	/* should we call subdriver's resume function? */
	callsub =
		(intf == ctx->control &&
		drvstate->subdriver &&
		drvstate->subdriver->resume);

	if (callsub)
		ret = drvstate->subdriver->resume(intf);
	if (ret < 0)
		goto err;
	ret = usbnet_resume(intf);
	if (ret < 0 && callsub)
		drvstate->subdriver->suspend(intf, PMSG_SUSPEND);
err:
	return ret;
}

static const struct driver_info huawei_cdc_ncm_info = {
	.description = "Huawei CDC NCM device",
	.flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN,
	.bind = huawei_cdc_ncm_bind,
	.unbind = huawei_cdc_ncm_unbind,
	.manage_power = huawei_cdc_ncm_manage_power,
	.rx_fixup = cdc_ncm_rx_fixup,
	.tx_fixup = huawei_ncm_tx_fixup,
};

static const struct usb_device_id huawei_cdc_ncm_devs[] = {
	/* Huawei NCM devices disguised as vendor specific */
	{ USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x16),
	  .driver_info = (unsigned long)&huawei_cdc_ncm_info,
	},
	{ USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x46),
	  .driver_info = (unsigned long)&huawei_cdc_ncm_info,
	},
	{ USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x76),
	  .driver_info = (unsigned long)&huawei_cdc_ncm_info,
	},
	{ USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x03, 0x16),
	  .driver_info = (unsigned long)&huawei_cdc_ncm_info,
	},

	/* Terminating entry */
	{
	},
};
MODULE_DEVICE_TABLE(usb, huawei_cdc_ncm_devs);

static struct usb_driver huawei_cdc_ncm_driver = {
	.name = "huawei_cdc_ncm",
	.id_table = huawei_cdc_ncm_devs,
	.probe = usbnet_probe,
	.disconnect = usbnet_disconnect,
	.suspend = huawei_cdc_ncm_suspend,
	.resume = huawei_cdc_ncm_resume,
	.reset_resume = huawei_cdc_ncm_resume,
	.supports_autosuspend = 1,
	.disable_hub_initiated_lpm = 1,
};
module_usb_driver(huawei_cdc_ncm_driver);
MODULE_AUTHOR("Enrico Mioso <mrkiko.rs@...il.com>");
MODULE_DESCRIPTION("USB CDC NCM host driver with encapsulated protocol support");
MODULE_LICENSE("GPL");

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ