Subject: Re: [PATCH 1/2] netfilter: xtables: inclusion of xt_SYSRQ

From: Patrick McHardy <[email-masked]>
Date: Wed, 21 Apr 2010 14:59:32 +0200

Jan Engelhardt wrote:
> The SYSRQ target will allow to remotely invoke sysrq on the local
> machine. Authentication is by means of a pre-shared key that can
> either be transmitted plaintext or digest-secured.

I really think this is pushing what netfilter is meant for a bit
far. Its basically abusing the firewall ruleset to offer a network
service.

I can see that its useful to have this in the kernel instead of
userspace, but why isn't this implemented as a stand-alone module?
That seems like a better design to me and also makes it more useful
by not depending on netfilter.

> Signed-off-by: Jan Engelhardt <[email-masked]>
> ---
>  net/netfilter/Kconfig    |   12 ++
>  net/netfilter/Makefile   |    1 +
>  net/netfilter/xt_SYSRQ.c |  354 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 367 insertions(+), 0 deletions(-)
>  create mode 100644 net/netfilter/xt_SYSRQ.c
> 
> diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig
> index 673a6c8..bfd9b6f 100644
> --- a/net/netfilter/Kconfig
> +++ b/net/netfilter/Kconfig
> @@ -502,6 +502,18 @@ config NETFILTER_XT_TARGET_RATEEST
>  
>  	  To compile it as a module, choose M here.  If unsure, say N.
>  
> +config NETFILTER_XT_TARGET_SYSRQ
> +	tristate '"SYSRQ" - remote sysrq invocation'
> +	depends on NETFILTER_ADVANCED
> +	---help---
> +	This option enables the "SYSRQ" target which can be used to trigger
> +	sysrq from a remote machine using a magic UDP packet with a pre-shared
> +	password. This is useful when the receiving host has locked up in an
> +	Oops yet still can process incoming packets.
> +
> +	Besides plaintext packets, digest-secured SYSRQ requests will be
> +	supported when CONFIG_CRYPTO is enabled.
> +
>  config NETFILTER_XT_TARGET_TEE
>  	tristate '"TEE" - packet cloning to alternate destiantion'
>  	depends on NETFILTER_ADVANCED
> diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile
> index 14e3a8f..f032195 100644
> --- a/net/netfilter/Makefile
> +++ b/net/netfilter/Makefile
> @@ -56,6 +56,7 @@ obj-$(CONFIG_NETFILTER_XT_TARGET_NFQUEUE) += xt_NFQUEUE.o
>  obj-$(CONFIG_NETFILTER_XT_TARGET_NOTRACK) += xt_NOTRACK.o
>  obj-$(CONFIG_NETFILTER_XT_TARGET_RATEEST) += xt_RATEEST.o
>  obj-$(CONFIG_NETFILTER_XT_TARGET_SECMARK) += xt_SECMARK.o
> +obj-$(CONFIG_NETFILTER_XT_TARGET_SYSRQ) += xt_SYSRQ.o
>  obj-$(CONFIG_NETFILTER_XT_TARGET_TPROXY) += xt_TPROXY.o
>  obj-$(CONFIG_NETFILTER_XT_TARGET_TCPMSS) += xt_TCPMSS.o
>  obj-$(CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP) += xt_TCPOPTSTRIP.o
> diff --git a/net/netfilter/xt_SYSRQ.c b/net/netfilter/xt_SYSRQ.c
> new file mode 100644
> index 0000000..929b204
> --- /dev/null
> +++ b/net/netfilter/xt_SYSRQ.c
> @@ -0,0 +1,354 @@
> +/*
> + *	"SYSRQ" target extension for Netfilter
> + *	Copyright Â© Jan Engelhardt <jengelh [at] medozas de>, 2008 - 2010
> + *
> + *	Based upon the ipt_SYSRQ idea by Marek Zalem <marek [at] terminus sk>
> + *
> + *	This program is free software; you can redistribute it and/or
> + *	modify it under the terms of the GNU General Public License
> + *	version 2 or later as published by the Free Software Foundation.
> + */
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +#include <linux/in.h>
> +#include <linux/ip.h>
> +#include <linux/ipv6.h>
> +#include <linux/module.h>
> +#include <linux/skbuff.h>
> +#include <linux/sysrq.h>
> +#include <linux/udp.h>
> +#include <linux/netfilter_ipv4/ip_tables.h>
> +#include <linux/netfilter_ipv6/ip6_tables.h>
> +#include <linux/netfilter/x_tables.h>
> +#include <linux/crypto.h>
> +#include <linux/scatterlist.h>
> +#include <net/ip.h>
> +
> +#if defined(CONFIG_CRYPTO) || defined(CRYPTO_CONFIG_MODULE)
> +#	define WITH_CRYPTO 1
> +#endif
> +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
> +#	define WITH_IPV6 1
> +#endif
> +
> +static bool sysrq_once;
> +static char sysrq_password[64];
> +static char sysrq_hash[16] = "sha1";
> +static long sysrq_seqno;
> +static int sysrq_debug;
> +module_param_string(password, sysrq_password, sizeof(sysrq_password),
> +	S_IRUSR | S_IWUSR);
> +module_param_string(hash, sysrq_hash, sizeof(sysrq_hash), S_IRUSR);
> +module_param_named(seqno, sysrq_seqno, long, S_IRUSR | S_IWUSR);
> +module_param_named(debug, sysrq_debug, int, S_IRUSR | S_IWUSR);
> +MODULE_PARM_DESC(password, "password for remote sysrq");
> +MODULE_PARM_DESC(hash, "hash algorithm, default sha1");
> +MODULE_PARM_DESC(seqno, "sequence number for remote sysrq");
> +MODULE_PARM_DESC(debug, "debugging: 0=off, 1=on");
> +
> +#ifdef WITH_CRYPTO
> +static struct crypto_hash *sysrq_tfm;
> +static int sysrq_digest_size;
> +static unsigned char *sysrq_digest_password;
> +static unsigned char *sysrq_digest;
> +static char *sysrq_hexdigest;
> +
> +/*
> + * The data is of the form "<requests>,<seqno>,<salt>,<hash>" where <requests>
> + * is a series of sysrq requests; <seqno> is a sequence number that must be
> + * greater than the last sequence number; <salt> is some random bytes; and
> + * <hash> is the hash of everything up to and including the preceding ","
> + * together with the password.
> + *
> + * For example
> + *
> + *   salt=$RANDOM
> + *   req="s,$(date +%s),$salt"
> + *   echo "$req,$(echo -n $req,secret | sha1sum | cut -c1-40)"
> + *
> + * You will want a better salt and password than that though :-)
> + */
> +static unsigned int sysrq_tg(const void *pdata, uint16_t len)
> +{
> +	const char *data = pdata;
> +	int i, n;
> +	struct scatterlist sg[2];
> +	struct hash_desc desc;
> +	int ret;
> +	long new_seqno = 0;
> +
> +	if (*sysrq_password == '\0') {
> +		if (!sysrq_once)
> +			pr_info("No password set\n");
> +		sysrq_once = true;
> +		return NF_DROP;
> +	}
> +	if (len == 0)
> +		return NF_DROP;
> +
> +	for (i = 0; sysrq_password[i] != '\0' &&
> +	     sysrq_password[i] != '\n'; ++i)
> +		/* loop */;
> +	sysrq_password[i] = '\0';
> +
> +	i = 0;
> +	for (n = 0; n < len - 1; ++n) {
> +		if (i == 1 && '0' <= data[n] && data[n] <= '9')
> +			new_seqno = 10L * new_seqno + data[n] - '0';
> +		if (data[n] == ',' && ++i == 3)
> +			break;
> +	}
> +	++n;
> +	if (i != 3) {
> +		if (sysrq_debug)
> +			pr_info("badly formatted request\n");
> +		return NF_DROP;
> +	}
> +	if (sysrq_seqno >= new_seqno) {
> +		if (sysrq_debug)
> +			pr_info("old sequence number ignored\n");
> +		return NF_DROP;
> +	}
> +
> +	desc.tfm   = sysrq_tfm;
> +	desc.flags = 0;
> +	ret = crypto_hash_init(&desc);
> +	if (ret != 0)
> +		goto hash_fail;
> +	sg_init_table(sg, 2);
> +	sg_set_buf(&sg[0], data, n);
> +	strcpy(sysrq_digest_password, sysrq_password);
> +	i = strlen(sysrq_digest_password);
> +	sg_set_buf(&sg[1], sysrq_digest_password, i);
> +	ret = crypto_hash_digest(&desc, sg, n + i, sysrq_digest);
> +	if (ret != 0)
> +		goto hash_fail;
> +
> +	for (i = 0; i < sysrq_digest_size; ++i) {
> +		sysrq_hexdigest[2*i] =
> +			"0123456789abcdef"[(sysrq_digest[i] >> 4) & 0xf];
> +		sysrq_hexdigest[2*i+1] =
> +			"0123456789abcdef"[sysrq_digest[i] & 0xf];
> +	}
> +	sysrq_hexdigest[2*sysrq_digest_size] = '\0';
> +	if (len - n < sysrq_digest_size) {
> +		if (sysrq_debug)
> +			pr_info("Short digest, expected %s\n",
> +				sysrq_hexdigest);
> +		return NF_DROP;
> +	}
> +	if (strncmp(data + n, sysrq_hexdigest, sysrq_digest_size) != 0) {
> +		if (sysrq_debug)
> +			pr_info("Bad digest, expected %s\n", sysrq_hexdigest);
> +		return NF_DROP;
> +	}
> +
> +	/* Now we trust the requester */
> +	sysrq_seqno = new_seqno;
> +	for (i = 0; i < len && data[i] != ','; ++i) {
> +		pr_info("SysRq %c\n", data[i]);
> +		handle_sysrq(data[i], NULL);
> +	}
> +	return NF_ACCEPT;
> +
> + hash_fail:
> +	pr_warning("digest failure\n");
> +	return NF_DROP;
> +}
> +#else
> +static unsigned int sysrq_tg(const void *pdata, uint16_t len)
> +{
> +	const char *data = pdata;
> +	char c;
> +
> +	if (*sysrq_password == '\0') {
> +		if (!sysrq_once)
> +			pr_info("No password set\n");
> +		sysrq_once = true;
> +		return NF_DROP;
> +	}
> +
> +	if (len == 0)
> +		return NF_DROP;
> +
> +	c = *data;
> +	if (strncmp(&data[1], sysrq_password, len - 1) != 0) {
> +		pr_warning("Failed attempt - password mismatch\n");
> +		return NF_DROP;
> +	}
> +
> +	handle_sysrq(c, NULL);
> +	return NF_ACCEPT;
> +}
> +#endif
> +
> +static unsigned int
> +sysrq_tg4(struct sk_buff *skb, const struct xt_target_param *par)
> +{
> +	const struct iphdr *iph;
> +	const struct udphdr *udph;
> +	uint16_t len;
> +
> +	if (skb_linearize(skb) < 0)
> +		return NF_DROP;
> +
> +	iph = ip_hdr(skb);
> +	if (iph->protocol != IPPROTO_UDP && iph->protocol != IPPROTO_UDPLITE)
> +		return NF_DROP;
> +
> +	udph = (const void *)iph + ip_hdrlen(skb);
> +	len  = ntohs(udph->len) - sizeof(struct udphdr);
> +
> +	if (sysrq_debug)
> +		pr_info(": %pI4:%u -> :%u len=%u\n", &iph->saddr,
> +			htons(udph->source), htons(udph->dest), len);
> +	return sysrq_tg((void *)udph + sizeof(struct udphdr), len);
> +}
> +
> +#ifdef WITH_IPV6
> +static unsigned int
> +sysrq_tg6(struct sk_buff *skb, const struct xt_target_param *par)
> +{
> +	const struct ipv6hdr *iph;
> +	const struct udphdr *udph;
> +	unsigned short frag_off;
> +	unsigned int th_off;
> +	uint16_t len;
> +
> +	if (skb_linearize(skb) < 0)
> +		return NF_DROP;
> +
> +	iph = ipv6_hdr(skb);
> +	if (ipv6_find_hdr(skb, &th_off, IPPROTO_UDP, &frag_off) < 0 ||
> +	    frag_off > 0)
> +		return NF_ACCEPT; /* sink it */
> +
> +	udph = (const void *)iph + th_off;
> +	len  = ntohs(udph->len) - sizeof(struct udphdr);
> +
> +	if (sysrq_debug)
> +		pr_info("%pI6:%hu -> :%hu len=%u\n", &iph->saddr,
> +			ntohs(udph->source), ntohs(udph->dest), len);
> +	return sysrq_tg(udph + sizeof(struct udphdr), len);
> +}
> +#endif
> +
> +static int sysrq_tg_check(const struct xt_tgchk_param *par)
> +{
> +	if (par->target->family == NFPROTO_IPV4) {
> +		const struct ipt_entry *entry = par->entryinfo;
> +
> +		if ((entry->ip.proto != IPPROTO_UDP &&
> +		    entry->ip.proto != IPPROTO_UDPLITE) ||
> +		    entry->ip.invflags & XT_INV_PROTO)
> +			goto out;
> +	} else if (par->target->family == NFPROTO_IPV6) {
> +		const struct ip6t_entry *entry = par->entryinfo;
> +
> +		if ((entry->ipv6.proto != IPPROTO_UDP &&
> +		    entry->ipv6.proto != IPPROTO_UDPLITE) ||
> +		    entry->ipv6.invflags & XT_INV_PROTO)
> +			goto out;
> +	}
> +
> +	return true;
> +
> + out:
> +	pr_info("only available for UDP and UDP-Lite");
> +	return false;
> +}
> +
> +static struct xt_target sysrq_tg_reg[] __read_mostly = {
> +	{
> +		.name       = "SYSRQ",
> +		.revision   = 1,
> +		.family     = NFPROTO_IPV4,
> +		.target     = sysrq_tg4,
> +		.checkentry = sysrq_tg_check,
> +		.me         = THIS_MODULE,
> +	},
> +#ifdef WITH_IPV6
> +	{
> +		.name       = "SYSRQ",
> +		.revision   = 1,
> +		.family     = NFPROTO_IPV6,
> +		.target     = sysrq_tg6,
> +		.checkentry = sysrq_tg_check,
> +		.me         = THIS_MODULE,
> +	},
> +#endif
> +};
> +
> +static void sysrq_crypto_exit(void)
> +{
> +#ifdef WITH_CRYPTO
> +	if (sysrq_tfm)
> +		crypto_free_hash(sysrq_tfm);
> +	if (sysrq_digest)
> +		kfree(sysrq_digest);
> +	if (sysrq_hexdigest)
> +		kfree(sysrq_hexdigest);
> +	if (sysrq_digest_password)
> +		kfree(sysrq_digest_password);
> +#endif
> +}
> +
> +static int __init sysrq_crypto_init(void)
> +{
> +#if defined(WITH_CRYPTO)
> +	struct timeval now;
> +	int ret;
> +
> +	sysrq_tfm = crypto_alloc_hash(sysrq_hash, 0, CRYPTO_ALG_ASYNC);
> +	if (IS_ERR(sysrq_tfm)) {
> +		pr_err("Could not find or load %s hash\n", sysrq_hash);
> +		sysrq_tfm = NULL;
> +		ret = PTR_ERR(sysrq_tfm);
> +		goto fail;
> +	}
> +	sysrq_digest_size = crypto_hash_digestsize(sysrq_tfm);
> +	sysrq_digest = kmalloc(sysrq_digest_size, GFP_KERNEL);
> +	ret = -ENOMEM;
> +	if (sysrq_digest == NULL)
> +		goto fail;
> +	sysrq_hexdigest = kmalloc(2 * sysrq_digest_size + 1, GFP_KERNEL);
> +	if (sysrq_hexdigest == NULL)
> +		goto fail;
> +	sysrq_digest_password = kmalloc(sizeof(sysrq_password), GFP_KERNEL);
> +	if (sysrq_digest_password == NULL)
> +		goto fail;
> +	do_gettimeofday(&now);
> +	sysrq_seqno = now.tv_sec;
> +	ret = xt_register_targets(sysrq_tg_reg, ARRAY_SIZE(sysrq_tg_reg));
> +	if (ret < 0)
> +		goto fail;
> +	return ret;
> +
> + fail:
> +	sysrq_crypto_exit();
> +	return ret;
> +#else
> +	pr_info("compiled without crypto\n");
> +#endif
> +	return -EINVAL;
> +}
> +
> +static int __init sysrq_tg_init(void)
> +{
> +	if (sysrq_crypto_init() < 0)
> +		pr_info("starting without crypto\n");
> +	return xt_register_targets(sysrq_tg_reg, ARRAY_SIZE(sysrq_tg_reg));
> +}
> +
> +static void __exit sysrq_tg_exit(void)
> +{
> +	sysrq_crypto_exit();
> +	xt_unregister_targets(sysrq_tg_reg, ARRAY_SIZE(sysrq_tg_reg));
> +}
> +
> +module_init(sysrq_tg_init);
> +module_exit(sysrq_tg_exit);
> +MODULE_DESCRIPTION("Xtables: triggering SYSRQ remotely");
> +MODULE_AUTHOR("Jan Engelhardt <[email-masked]>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("ipt_SYSRQ");
> +MODULE_ALIAS("ip6t_SYSRQ");

--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to [email-masked]
More majordomo info at  http://vger.kernel.org/majordomo-info.html