mirror of
git://git.code.sf.net/p/xtables-addons/xtables-addons
synced 2025-09-05 12:16:38 +02:00
373 lines
9.7 KiB
C
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");
|