Files
xtables-addons/extensions/xt_SYSRQ.c
Jan Engelhardt 0021003dc7 extensions: abolish NIPQUAD/NIP6
Support for Linux 2.6.28 is long gone.
2020-11-19 12:54:36 +01:00

373 lines
9.7 KiB
C

/*
* "SYSRQ" target extension for Xtables
* Copyright Jan Engelhardt, 2016
*
* Based upon the ipt_SYSRQ idea by Marek Zalem <marek [at] terminus sk>
*
* Security additions John Haxby <john.haxby [at] oracle com>
*
* 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 3 as published by the Free Software Foundation.
*/
#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/version.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter/x_tables.h>
#include <crypto/hash.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include "compat_xtables.h"
#if defined(CONFIG_CRYPTO) || defined(CONFIG_CRYPTO_MODULE)
# define WITH_CRYPTO 1
#endif
#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_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_shash *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 "<dstaddr>,<password>".
*
* For example
*
* salt=$RANDOM
* req="s,$(date +%s),$salt"
* echo "$req,$(echo -n $req,10.10.25.1,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 shash_desc desc;
int ret;
long new_seqno = 0;
if (*sysrq_password == '\0') {
if (!sysrq_once)
printk(KERN_INFO KBUILD_MODNAME ": 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)
printk(KERN_WARNING KBUILD_MODNAME
": badly formatted request\n");
return NF_DROP;
}
if (sysrq_seqno >= new_seqno) {
if (sysrq_debug)
printk(KERN_WARNING KBUILD_MODNAME
": old sequence number ignored\n");
return NF_DROP;
}
desc.tfm = sysrq_tfm;
ret = crypto_shash_init(&desc);
if (ret != 0)
goto hash_fail;
if (crypto_shash_update(&desc, data, n) != 0)
goto hash_fail;
if (crypto_shash_update(&desc, sysrq_digest_password,
strlen(sysrq_digest_password)) != 0)
goto hash_fail;
if (crypto_shash_final(&desc, sysrq_digest) != 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 * 2) {
if (sysrq_debug)
printk(KERN_INFO KBUILD_MODNAME ": Short digest,"
" expected %s\n", sysrq_hexdigest);
return NF_DROP;
}
if (strncmp(data + n, sysrq_hexdigest, sysrq_digest_size * 2) != 0) {
if (sysrq_debug)
printk(KERN_INFO KBUILD_MODNAME ": 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) {
printk(KERN_INFO KBUILD_MODNAME ": SysRq %c\n", data[i]);
handle_sysrq(data[i]);
}
return NF_ACCEPT;
hash_fail:
printk(KERN_WARNING KBUILD_MODNAME ": 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)
printk(KERN_INFO KBUILD_MODNAME "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) {
printk(KERN_INFO KBUILD_MODNAME "Failed attempt - "
"password mismatch\n");
return NF_DROP;
}
handle_sysrq(c);
return NF_ACCEPT;
}
#endif
static unsigned int
sysrq_tg4(struct sk_buff *skb, const struct xt_action_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)
printk(KERN_INFO KBUILD_MODNAME
": %pI4:%hu -> :%hu len=%u\n",
&iph->saddr, htons(udph->source),
htons(udph->dest), len);
#ifdef WITH_CRYPTO
sprintf(sysrq_digest_password, "%pI4,%s", &iph->daddr, sysrq_password);
#endif
return sysrq_tg((void *)udph + sizeof(struct udphdr), len);
}
#ifdef WITH_IPV6
static unsigned int
sysrq_tg6(struct sk_buff *skb, const struct xt_action_param *par)
{
const struct ipv6hdr *iph;
const struct udphdr *udph;
unsigned short frag_off;
unsigned int th_off = 0;
uint16_t len;
if (skb_linearize(skb) < 0)
return NF_DROP;
iph = ipv6_hdr(skb);
/* Should probably be using %IP6T_FH_F_AUTH */
if ((ipv6_find_hdr(skb, &th_off, IPPROTO_UDP, &frag_off, NULL) < 0 &&
ipv6_find_hdr(skb, &th_off, IPPROTO_UDPLITE, &frag_off, NULL) < 0) ||
frag_off > 0)
return NF_DROP;
udph = (const void *)iph + th_off;
len = ntohs(udph->len) - sizeof(struct udphdr);
if (sysrq_debug)
printk(KERN_INFO KBUILD_MODNAME ": %pI6:%hu -> :%hu len=%u\n",
&iph->saddr, ntohs(udph->source),
ntohs(udph->dest), len);
#ifdef WITH_CRYPTO
sprintf(sysrq_digest_password, "%pI6,%s", &iph->daddr, sysrq_password);
#endif
return sysrq_tg((void *)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 0;
out:
printk(KERN_ERR KBUILD_MODNAME ": only available for UDP and UDP-Lite");
return -EINVAL;
}
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_shash(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 timespec64 now;
int ret;
sysrq_tfm = crypto_alloc_shash(sysrq_hash, 0, 0);
if (IS_ERR(sysrq_tfm)) {
printk(KERN_WARNING KBUILD_MODNAME
": Error: Could not find or load %s hash\n",
sysrq_hash);
ret = PTR_ERR(sysrq_tfm);
sysrq_tfm = NULL;
goto fail;
}
sysrq_digest_size = crypto_shash_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("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255") +
sizeof(sysrq_password), GFP_KERNEL);
if (sysrq_digest_password == NULL)
goto fail;
ktime_get_real_ts64(&now);
sysrq_seqno = now.tv_sec;
return 0;
fail:
sysrq_crypto_exit();
return ret;
#else
printk(KERN_WARNING "Kernel was compiled without crypto, "
"so xt_SYSRQ won't use crypto.\n");
#endif
return -EINVAL;
}
static int __init sysrq_tg_init(void)
{
if (sysrq_crypto_init() < 0)
printk(KERN_WARNING "xt_SYSRQ 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");
MODULE_AUTHOR("John Haxby <john.haxby@oracle.com");
MODULE_LICENSE("GPL");
MODULE_ALIAS("ipt_SYSRQ");
MODULE_ALIAS("ip6t_SYSRQ");