Commit bfb15f2a authored by Fernando Fernandez Mancera's avatar Fernando Fernandez Mancera Committed by Pablo Neira Ayuso

netfilter: extract Passive OS fingerprint infrastructure from xt_osf

Add nf_osf_ttl() and nf_osf_match() into nf_osf.c to prepare for
nf_tables support.
Signed-off-by: default avatarFernando Fernandez Mancera <ffmancera@riseup.net>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent 3f9c56a5
#include <uapi/linux/netfilter/nf_osf.h>
/* Initial window size option state machine: multiple of mss, mtu or
* plain numeric value. Can also be made as plain numeric value which
* is not a multiple of specified value.
*/
enum nf_osf_window_size_options {
OSF_WSS_PLAIN = 0,
OSF_WSS_MSS,
OSF_WSS_MTU,
OSF_WSS_MODULO,
OSF_WSS_MAX,
};
enum osf_fmatch_states {
/* Packet does not match the fingerprint */
FMATCH_WRONG = 0,
/* Packet matches the fingerprint */
FMATCH_OK,
/* Options do not match the fingerprint, but header does */
FMATCH_OPT_WRONG,
};
bool nf_osf_match(const struct sk_buff *skb, u_int8_t family,
int hooknum, struct net_device *in, struct net_device *out,
const struct nf_osf_info *info, struct net *net,
const struct list_head *nf_osf_fingers);
#ifndef _NF_OSF_H
#define _NF_OSF_H
#define MAXGENRELEN 32
#define NF_OSF_GENRE (1 << 0)
#define NF_OSF_TTL (1 << 1)
#define NF_OSF_LOG (1 << 2)
#define NF_OSF_INVERT (1 << 3)
#define NF_OSF_LOGLEVEL_ALL 0 /* log all matched fingerprints */
#define NF_OSF_LOGLEVEL_FIRST 1 /* log only the first matced fingerprint */
#define NF_OSF_LOGLEVEL_ALL_KNOWN 2 /* do not log unknown packets */
#define NF_OSF_TTL_TRUE 0 /* True ip and fingerprint TTL comparison */
/* Do not compare ip and fingerprint TTL at all */
#define NF_OSF_TTL_NOCHECK 2
/* Wildcard MSS (kind of).
* It is used to implement a state machine for the different wildcard values
* of the MSS and window sizes.
*/
struct nf_osf_wc {
__u32 wc;
__u32 val;
};
/* This struct represents IANA options
* http://www.iana.org/assignments/tcp-parameters
*/
struct nf_osf_opt {
__u16 kind, length;
struct nf_osf_wc wc;
};
struct nf_osf_info {
char genre[MAXGENRELEN];
__u32 len;
__u32 flags;
__u32 loglevel;
__u32 ttl;
};
struct nf_osf_user_finger {
struct nf_osf_wc wss;
__u8 ttl, df;
__u16 ss, mss;
__u16 opt_num;
char genre[MAXGENRELEN];
char version[MAXGENRELEN];
char subtype[MAXGENRELEN];
/* MAX_IPOPTLEN is maximum if all options are NOPs or EOLs */
struct nf_osf_opt opt[MAX_IPOPTLEN];
};
struct nf_osf_finger {
struct rcu_head rcu_head;
struct list_head finger_entry;
struct nf_osf_user_finger finger;
};
struct nf_osf_nlmsg {
struct nf_osf_user_finger f;
struct iphdr ip;
struct tcphdr tcp;
};
/* Defines for IANA option kinds */
enum iana_options {
OSFOPT_EOL = 0, /* End of options */
OSFOPT_NOP, /* NOP */
OSFOPT_MSS, /* Maximum segment size */
OSFOPT_WSO, /* Window scale option */
OSFOPT_SACKP, /* SACK permitted */
OSFOPT_SACK, /* SACK */
OSFOPT_ECHO,
OSFOPT_ECHOREPLY,
OSFOPT_TS, /* Timestamp option */
OSFOPT_POCP, /* Partial Order Connection Permitted */
OSFOPT_POSP, /* Partial Order Service Profile */
/* Others are not used in the current OSF */
OSFOPT_EMPTY = 255,
};
#endif /* _NF_OSF_H */
...@@ -23,101 +23,29 @@ ...@@ -23,101 +23,29 @@
#include <linux/types.h> #include <linux/types.h>
#include <linux/ip.h> #include <linux/ip.h>
#include <linux/tcp.h> #include <linux/tcp.h>
#include <linux/netfilter/nf_osf.h>
#define MAXGENRELEN 32 #define XT_OSF_GENRE NF_OSF_GENRE
#define XT_OSF_INVERT NF_OSF_INVERT
#define XT_OSF_GENRE (1<<0) #define XT_OSF_TTL NF_OSF_TTL
#define XT_OSF_TTL (1<<1) #define XT_OSF_LOG NF_OSF_LOG
#define XT_OSF_LOG (1<<2)
#define XT_OSF_INVERT (1<<3)
#define XT_OSF_LOGLEVEL_ALL 0 /* log all matched fingerprints */ #define XT_OSF_LOGLEVEL_ALL NF_OSF_LOGLEVEL_ALL
#define XT_OSF_LOGLEVEL_FIRST 1 /* log only the first matced fingerprint */ #define XT_OSF_LOGLEVEL_FIRST NF_OSF_LOGLEVEL_FIRST
#define XT_OSF_LOGLEVEL_ALL_KNOWN 2 /* do not log unknown packets */ #define XT_OSF_LOGLEVEL_ALL_KNOWN NF_OSF_LOGLEVEL_ALL_KNOWN
#define XT_OSF_TTL_TRUE 0 /* True ip and fingerprint TTL comparison */ #define XT_OSF_TTL_TRUE NF_OSF_TTL_TRUE
#define XT_OSF_TTL_LESS 1 /* Check if ip TTL is less than fingerprint one */ #define XT_OSF_TTL_NOCHECK NF_OSF_TTL_NOCHECK
#define XT_OSF_TTL_NOCHECK 2 /* Do not compare ip and fingerprint TTL at all */
struct xt_osf_info {
char genre[MAXGENRELEN];
__u32 len;
__u32 flags;
__u32 loglevel;
__u32 ttl;
};
/*
* Wildcard MSS (kind of).
* It is used to implement a state machine for the different wildcard values
* of the MSS and window sizes.
*/
struct xt_osf_wc {
__u32 wc;
__u32 val;
};
/*
* This struct represents IANA options
* http://www.iana.org/assignments/tcp-parameters
*/
struct xt_osf_opt {
__u16 kind, length;
struct xt_osf_wc wc;
};
struct xt_osf_user_finger {
struct xt_osf_wc wss;
__u8 ttl, df;
__u16 ss, mss;
__u16 opt_num;
char genre[MAXGENRELEN]; #define XT_OSF_TTL_LESS 1 /* Check if ip TTL is less than fingerprint one */
char version[MAXGENRELEN];
char subtype[MAXGENRELEN];
/* MAX_IPOPTLEN is maximum if all options are NOPs or EOLs */
struct xt_osf_opt opt[MAX_IPOPTLEN];
};
struct xt_osf_nlmsg {
struct xt_osf_user_finger f;
struct iphdr ip;
struct tcphdr tcp;
};
/* Defines for IANA option kinds */
enum iana_options {
OSFOPT_EOL = 0, /* End of options */
OSFOPT_NOP, /* NOP */
OSFOPT_MSS, /* Maximum segment size */
OSFOPT_WSO, /* Window scale option */
OSFOPT_SACKP, /* SACK permitted */
OSFOPT_SACK, /* SACK */
OSFOPT_ECHO,
OSFOPT_ECHOREPLY,
OSFOPT_TS, /* Timestamp option */
OSFOPT_POCP, /* Partial Order Connection Permitted */
OSFOPT_POSP, /* Partial Order Service Profile */
/* Others are not used in the current OSF */
OSFOPT_EMPTY = 255,
};
/* #define xt_osf_wc nf_osf_wc
* Initial window size option state machine: multiple of mss, mtu or #define xt_osf_opt nf_osf_opt
* plain numeric value. Can also be made as plain numeric value which #define xt_osf_info nf_osf_info
* is not a multiple of specified value. #define xt_osf_user_finger nf_osf_user_finger
*/ #define xt_osf_finger nf_osf_finger
enum xt_osf_window_size_options { #define xt_osf_nlmsg nf_osf_nlmsg
OSF_WSS_PLAIN = 0,
OSF_WSS_MSS,
OSF_WSS_MTU,
OSF_WSS_MODULO,
OSF_WSS_MAX,
};
/* /*
* Add/remove fingerprint from the kernel. * Add/remove fingerprint from the kernel.
......
...@@ -444,6 +444,9 @@ config NETFILTER_SYNPROXY ...@@ -444,6 +444,9 @@ config NETFILTER_SYNPROXY
endif # NF_CONNTRACK endif # NF_CONNTRACK
config NF_OSF
tristate 'Passive OS fingerprint infrastructure'
config NF_TABLES config NF_TABLES
select NETFILTER_NETLINK select NETFILTER_NETLINK
tristate "Netfilter nf_tables support" tristate "Netfilter nf_tables support"
...@@ -1358,6 +1361,7 @@ config NETFILTER_XT_MATCH_NFACCT ...@@ -1358,6 +1361,7 @@ config NETFILTER_XT_MATCH_NFACCT
config NETFILTER_XT_MATCH_OSF config NETFILTER_XT_MATCH_OSF
tristate '"osf" Passive OS fingerprint match' tristate '"osf" Passive OS fingerprint match'
depends on NETFILTER_ADVANCED && NETFILTER_NETLINK depends on NETFILTER_ADVANCED && NETFILTER_NETLINK
select NF_OSF
help help
This option selects the Passive OS Fingerprinting match module This option selects the Passive OS Fingerprinting match module
that allows to passively match the remote operating system by that allows to passively match the remote operating system by
......
...@@ -101,6 +101,7 @@ obj-$(CONFIG_NFT_HASH) += nft_hash.o ...@@ -101,6 +101,7 @@ obj-$(CONFIG_NFT_HASH) += nft_hash.o
obj-$(CONFIG_NFT_FIB) += nft_fib.o obj-$(CONFIG_NFT_FIB) += nft_fib.o
obj-$(CONFIG_NFT_FIB_INET) += nft_fib_inet.o obj-$(CONFIG_NFT_FIB_INET) += nft_fib_inet.o
obj-$(CONFIG_NFT_FIB_NETDEV) += nft_fib_netdev.o obj-$(CONFIG_NFT_FIB_NETDEV) += nft_fib_netdev.o
obj-$(CONFIG_NF_OSF) += nf_osf.o
# nf_tables netdev # nf_tables netdev
obj-$(CONFIG_NFT_DUP_NETDEV) += nft_dup_netdev.o obj-$(CONFIG_NFT_DUP_NETDEV) += nft_dup_netdev.o
......
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/capability.h>
#include <linux/if.h>
#include <linux/inetdevice.h>
#include <linux/ip.h>
#include <linux/list.h>
#include <linux/rculist.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/tcp.h>
#include <net/ip.h>
#include <net/tcp.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/x_tables.h>
#include <net/netfilter/nf_log.h>
#include <linux/netfilter/nf_osf.h>
static inline int nf_osf_ttl(const struct sk_buff *skb,
const struct nf_osf_info *info,
unsigned char f_ttl)
{
const struct iphdr *ip = ip_hdr(skb);
if (info->flags & NF_OSF_TTL) {
if (info->ttl == NF_OSF_TTL_TRUE)
return ip->ttl == f_ttl;
if (info->ttl == NF_OSF_TTL_NOCHECK)
return 1;
else if (ip->ttl <= f_ttl)
return 1;
else {
struct in_device *in_dev = __in_dev_get_rcu(skb->dev);
int ret = 0;
for_ifa(in_dev) {
if (inet_ifa_match(ip->saddr, ifa)) {
ret = (ip->ttl == f_ttl);
break;
}
}
endfor_ifa(in_dev);
return ret;
}
}
return ip->ttl == f_ttl;
}
bool
nf_osf_match(const struct sk_buff *skb, u_int8_t family,
int hooknum, struct net_device *in, struct net_device *out,
const struct nf_osf_info *info, struct net *net,
const struct list_head *nf_osf_fingers)
{
const unsigned char *optp = NULL, *_optp = NULL;
unsigned int optsize = 0, check_WSS = 0;
int fmatch = FMATCH_WRONG, fcount = 0;
const struct iphdr *ip = ip_hdr(skb);
const struct nf_osf_user_finger *f;
unsigned char opts[MAX_IPOPTLEN];
const struct nf_osf_finger *kf;
u16 window, totlen, mss = 0;
const struct tcphdr *tcp;
struct tcphdr _tcph;
bool df;
tcp = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(struct tcphdr), &_tcph);
if (!tcp)
return false;
if (!tcp->syn)
return false;
totlen = ntohs(ip->tot_len);
df = ntohs(ip->frag_off) & IP_DF;
window = ntohs(tcp->window);
if (tcp->doff * 4 > sizeof(struct tcphdr)) {
optsize = tcp->doff * 4 - sizeof(struct tcphdr);
_optp = optp = skb_header_pointer(skb, ip_hdrlen(skb) +
sizeof(struct tcphdr), optsize, opts);
}
list_for_each_entry_rcu(kf, &nf_osf_fingers[df], finger_entry) {
int foptsize, optnum;
f = &kf->finger;
if (!(info->flags & NF_OSF_LOG) && strcmp(info->genre, f->genre))
continue;
optp = _optp;
fmatch = FMATCH_WRONG;
if (totlen != f->ss || !nf_osf_ttl(skb, info, f->ttl))
continue;
/*
* Should not happen if userspace parser was written correctly.
*/
if (f->wss.wc >= OSF_WSS_MAX)
continue;
/* Check options */
foptsize = 0;
for (optnum = 0; optnum < f->opt_num; ++optnum)
foptsize += f->opt[optnum].length;
if (foptsize > MAX_IPOPTLEN ||
optsize > MAX_IPOPTLEN ||
optsize != foptsize)
continue;
check_WSS = f->wss.wc;
for (optnum = 0; optnum < f->opt_num; ++optnum) {
if (f->opt[optnum].kind == (*optp)) {
__u32 len = f->opt[optnum].length;
const __u8 *optend = optp + len;
fmatch = FMATCH_OK;
switch (*optp) {
case OSFOPT_MSS:
mss = optp[3];
mss <<= 8;
mss |= optp[2];
mss = ntohs((__force __be16)mss);
break;
case OSFOPT_TS:
break;
}
optp = optend;
} else
fmatch = FMATCH_OPT_WRONG;
if (fmatch != FMATCH_OK)
break;
}
if (fmatch != FMATCH_OPT_WRONG) {
fmatch = FMATCH_WRONG;
switch (check_WSS) {
case OSF_WSS_PLAIN:
if (f->wss.val == 0 || window == f->wss.val)
fmatch = FMATCH_OK;
break;
case OSF_WSS_MSS:
/*
* Some smart modems decrease mangle MSS to
* SMART_MSS_2, so we check standard, decreased
* and the one provided in the fingerprint MSS
* values.
*/
#define SMART_MSS_1 1460
#define SMART_MSS_2 1448
if (window == f->wss.val * mss ||
window == f->wss.val * SMART_MSS_1 ||
window == f->wss.val * SMART_MSS_2)
fmatch = FMATCH_OK;
break;
case OSF_WSS_MTU:
if (window == f->wss.val * (mss + 40) ||
window == f->wss.val * (SMART_MSS_1 + 40) ||
window == f->wss.val * (SMART_MSS_2 + 40))
fmatch = FMATCH_OK;
break;
case OSF_WSS_MODULO:
if ((window % f->wss.val) == 0)
fmatch = FMATCH_OK;
break;
}
}
if (fmatch != FMATCH_OK)
continue;
fcount++;
if (info->flags & NF_OSF_LOG)
nf_log_packet(net, family, hooknum, skb,
in, out, NULL,
"%s [%s:%s] : %pI4:%d -> %pI4:%d hops=%d\n",
f->genre, f->version, f->subtype,
&ip->saddr, ntohs(tcp->source),
&ip->daddr, ntohs(tcp->dest),
f->ttl - ip->ttl);
if ((info->flags & NF_OSF_LOG) &&
info->loglevel == NF_OSF_LOGLEVEL_FIRST)
break;
}
if (!fcount && (info->flags & NF_OSF_LOG))
nf_log_packet(net, family, hooknum, skb, in, out, NULL,
"Remote OS is not known: %pI4:%u -> %pI4:%u\n",
&ip->saddr, ntohs(tcp->source),
&ip->daddr, ntohs(tcp->dest));
if (fcount)
fmatch = FMATCH_OK;
return fmatch == FMATCH_OK;
}
EXPORT_SYMBOL_GPL(nf_osf_match);
MODULE_LICENSE("GPL");
...@@ -37,21 +37,6 @@ ...@@ -37,21 +37,6 @@
#include <net/netfilter/nf_log.h> #include <net/netfilter/nf_log.h>
#include <linux/netfilter/xt_osf.h> #include <linux/netfilter/xt_osf.h>
struct xt_osf_finger {
struct rcu_head rcu_head;
struct list_head finger_entry;
struct xt_osf_user_finger finger;
};
enum osf_fmatch_states {
/* Packet does not match the fingerprint */
FMATCH_WRONG = 0,
/* Packet matches the fingerprint */
FMATCH_OK,
/* Options do not match the fingerprint, but header does */
FMATCH_OPT_WRONG,
};
/* /*
* Indexed by dont-fragment bit. * Indexed by dont-fragment bit.
* It is the only constant value in the fingerprint. * It is the only constant value in the fingerprint.
...@@ -164,200 +149,17 @@ static const struct nfnetlink_subsystem xt_osf_nfnetlink = { ...@@ -164,200 +149,17 @@ static const struct nfnetlink_subsystem xt_osf_nfnetlink = {
.cb = xt_osf_nfnetlink_callbacks, .cb = xt_osf_nfnetlink_callbacks,
}; };
static inline int xt_osf_ttl(const struct sk_buff *skb, const struct xt_osf_info *info,
unsigned char f_ttl)
{
const struct iphdr *ip = ip_hdr(skb);
if (info->flags & XT_OSF_TTL) {
if (info->ttl == XT_OSF_TTL_TRUE)
return ip->ttl == f_ttl;
if (info->ttl == XT_OSF_TTL_NOCHECK)
return 1;
else if (ip->ttl <= f_ttl)
return 1;
else {
struct in_device *in_dev = __in_dev_get_rcu(skb->dev);
int ret = 0;
for_ifa(in_dev) {
if (inet_ifa_match(ip->saddr, ifa)) {
ret = (ip->ttl == f_ttl);
break;
}
}
endfor_ifa(in_dev);
return ret;
}
}
return ip->ttl == f_ttl;
}
static bool static bool
xt_osf_match_packet(const struct sk_buff *skb, struct xt_action_param *p) xt_osf_match_packet(const struct sk_buff *skb, struct xt_action_param *p)
{ {
const struct xt_osf_info *info = p->matchinfo; const struct xt_osf_info *info = p->matchinfo;
const struct iphdr *ip = ip_hdr(skb);
const struct tcphdr *tcp;
struct tcphdr _tcph;
int fmatch = FMATCH_WRONG, fcount = 0;
unsigned int optsize = 0, check_WSS = 0;
u16 window, totlen, mss = 0;
bool df;
const unsigned char *optp = NULL, *_optp = NULL;
unsigned char opts[MAX_IPOPTLEN];
const struct xt_osf_finger *kf;
const struct xt_osf_user_finger *f;
struct net *net = xt_net(p); struct net *net = xt_net(p);
if (!info) if (!info)
return false; return false;
tcp = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(struct tcphdr), &_tcph); return nf_osf_match(skb, xt_family(p), xt_hooknum(p), xt_in(p),
if (!tcp) xt_out(p), info, net, xt_osf_fingers);
return false;
if (!tcp->syn)
return false;
totlen = ntohs(ip->tot_len);
df = ntohs(ip->frag_off) & IP_DF;
window = ntohs(tcp->window);
if (tcp->doff * 4 > sizeof(struct tcphdr)) {
optsize = tcp->doff * 4 - sizeof(struct tcphdr);
_optp = optp = skb_header_pointer(skb, ip_hdrlen(skb) +
sizeof(struct tcphdr), optsize, opts);
}
list_for_each_entry_rcu(kf, &xt_osf_fingers[df], finger_entry) {
int foptsize, optnum;
f = &kf->finger;
if (!(info->flags & XT_OSF_LOG) && strcmp(info->genre, f->genre))
continue;
optp = _optp;
fmatch = FMATCH_WRONG;
if (totlen != f->ss || !xt_osf_ttl(skb, info, f->ttl))
continue;
/*
* Should not happen if userspace parser was written correctly.
*/
if (f->wss.wc >= OSF_WSS_MAX)
continue;
/* Check options */
foptsize = 0;
for (optnum = 0; optnum < f->opt_num; ++optnum)
foptsize += f->opt[optnum].length;
if (foptsize > MAX_IPOPTLEN ||
optsize > MAX_IPOPTLEN ||
optsize != foptsize)
continue;
check_WSS = f->wss.wc;
for (optnum = 0; optnum < f->opt_num; ++optnum) {
if (f->opt[optnum].kind == (*optp)) {
__u32 len = f->opt[optnum].length;
const __u8 *optend = optp + len;
fmatch = FMATCH_OK;
switch (*optp) {
case OSFOPT_MSS:
mss = optp[3];
mss <<= 8;
mss |= optp[2];
mss = ntohs((__force __be16)mss);
break;
case OSFOPT_TS:
break;
}
optp = optend;
} else
fmatch = FMATCH_OPT_WRONG;
if (fmatch != FMATCH_OK)
break;
}
if (fmatch != FMATCH_OPT_WRONG) {
fmatch = FMATCH_WRONG;
switch (check_WSS) {
case OSF_WSS_PLAIN:
if (f->wss.val == 0 || window == f->wss.val)
fmatch = FMATCH_OK;
break;
case OSF_WSS_MSS:
/*
* Some smart modems decrease mangle MSS to
* SMART_MSS_2, so we check standard, decreased
* and the one provided in the fingerprint MSS
* values.
*/
#define SMART_MSS_1 1460
#define SMART_MSS_2 1448
if (window == f->wss.val * mss ||
window == f->wss.val * SMART_MSS_1 ||
window == f->wss.val * SMART_MSS_2)
fmatch = FMATCH_OK;
break;
case OSF_WSS_MTU:
if (window == f->wss.val * (mss + 40) ||
window == f->wss.val * (SMART_MSS_1 + 40) ||
window == f->wss.val * (SMART_MSS_2 + 40))
fmatch = FMATCH_OK;
break;
case OSF_WSS_MODULO:
if ((window % f->wss.val) == 0)
fmatch = FMATCH_OK;
break;
}
}
if (fmatch != FMATCH_OK)
continue;
fcount++;
if (info->flags & XT_OSF_LOG)
nf_log_packet(net, xt_family(p), xt_hooknum(p), skb,
xt_in(p), xt_out(p), NULL,
"%s [%s:%s] : %pI4:%d -> %pI4:%d hops=%d\n",
f->genre, f->version, f->subtype,
&ip->saddr, ntohs(tcp->source),
&ip->daddr, ntohs(tcp->dest),
f->ttl - ip->ttl);
if ((info->flags & XT_OSF_LOG) &&
info->loglevel == XT_OSF_LOGLEVEL_FIRST)
break;
}
if (!fcount && (info->flags & XT_OSF_LOG))
nf_log_packet(net, xt_family(p), xt_hooknum(p), skb, xt_in(p),
xt_out(p), NULL,
"Remote OS is not known: %pI4:%u -> %pI4:%u\n",
&ip->saddr, ntohs(tcp->source),
&ip->daddr, ntohs(tcp->dest));
if (fcount)
fmatch = FMATCH_OK;
return fmatch == FMATCH_OK;
} }
static struct xt_match xt_osf_match = { static struct xt_match xt_osf_match = {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment