2022-01-24 19:08:16

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 00/20] tcp: Initial support for RFC5925 auth option

This is similar to TCP-MD5 in functionality but it's sufficiently
different that packet formats and interfaces are incompatible.
Compared to TCP-MD5 more algorithms are supported and multiple keys
can be used on the same connection but there is still no negotiation
mechanism.

Expected use-case is protecting long-duration BGP/LDP connections
between routers using pre-shared keys. The goal of this series is to
allow routers using the Linux TCP stack to interoperate with vendors
such as Cisco and Juniper.

Both algorithms described in RFC5926 are implemented but the code is not
very easily extensible beyond that. In particular there are several code
paths making stack allocations based on RFC5926 maximum, those would
have to be increased. Support for arbitrary algorithms was requested
in reply to previous posts but I believe there is no real use case for
that.

The current implementation is somewhat loose regarding configuration:
* Overlaping MKTs can be configured despite what RFC5925 says
* Current key can be deleted. RFC says this shouldn't be allowed but
enforcing this belongs at an admin shell rather than in the kernel.
* If multiple keys are valid for a destination the kernel picks one
in an unpredictable manner (this can be overridden).

These conditions could be tightened but it is not clear the kernel
should spend effort to prevent misconfiguration from userspace.

The current code is largely feature complete and well-tested but I am
somewhat stuck on next steps for getting this into upstream. Any
suggestions would be very welcome

Here are some known flaws and limitations:

* Unsigned packets are sent if AO is active but no keys are found.
* Crypto API is used with buffers on the stack and inside struct sock,
this might not work on all arches. I'm currently only testing x64 VMs
* Interaction with TCP-MD5 not tested in all corners.
* Interaction with FASTOPEN not tested and unlikely to work because
sequence number assumptions for syn/ack.
* No way to limit keys on a per-port basis (used to be implicit with
per-socket keys).
* Not clear if crypto_ahash_setkey might sleep. If some implementation
do that then maybe they could be excluded through alloc flags.
* Traffic key is not cached (reducing performance)
* No caching or hashing for key lookups so this will scale poorly with
many keys

There is relatively little code sharing with the TCP_MD5SIG feature and
earlier versions shared even less. Unlike MD5 the AO feature is kept
separate from the rest of the TCP code and reusing code would require
many unrelated cleanup changes.

I'm not convinced that "authopt" is particularly good naming convention,
this name is a personal invention that does not appear anywhere else.
The RFC calls this "tcp-ao". Perhaps TCP_AOSIG would be a better name
and it would also make the close connection to TCP_MD5SIG more visible?

Some testing support is included in nettest and fcnal-test.sh, similar
to the current level of tcp-md5 testing.

A more elaborate test suite using pytest and scapy is available out of
tree: https://github.com/cdleonard/tcp-authopt-test That test suite is
much larger that the kernel code and did not receive many comments in
previous ports so I will attempt to push it separately (if at all).

Changes for frr (old): https://github.com/FRRouting/frr/pull/9442
That PR was made early for ABI feedback, it has many issues.

Changes for yabgp (old): https://github.com/cdleonard/yabgp/commits/tcp_authopt
This can be used for easy interoperability testing with cisco/juniper/etc.
Would need updates for global keys to avoid leaks

Changes since PATCH v4:
* Move the traffic_key context_bytes header to stack. If it's a constant
string then ahash can fail unexpectedly.
* Fix allowing unsigned traffic if all keys are marked norecv.
* Fix crashing in __tcp_authopt_alg_init on failure.
* Try to respect the rnextkeyid from SYN on SYNACK (new patch)
* Fix incorrect check for TCP_AUTHOPT_KEY_DEL in __tcp_authopt_select_key
* Improve docs on __tcp_authopt_select_key
* Fix build with CONFIG_PROC_FS=n (kernel build robot)
* Fix build with CONFIG_IPV6=n (kernel build robot)
Link: https://lore.kernel.org/netdev/[email protected]/

Changes since PATCH v3:
* Made keys global (per-netns rather than per-sock).
* Add /proc/net/tcp_authopt with a table of keys (not sockets).
* Fix part of the shash/ahash conversion having slipped from patch 3 to patch 5
* Fix tcp_parse_sig_options assigning NULL incorrectly when both MD5 and AO
are disabled (kernel build robot)
* Fix sparse endianness warnings in prefix match (kernel build robot)
* Fix several incorrect RCU annotations reported by sparse (kernel build robot)
Link: https://lore.kernel.org/netdev/[email protected]/

Changes since PATCH v2:
* Protect tcp_authopt_alg_get/put_tfm with local_bh_disable instead of
preempt_disable. This caused signature corruption when send path executing
with BH enabled was interrupted by recv.
* Fix accepted keyids not configured locally as "unexpected". If any key
is configured that matches the peer then traffic MUST be signed.
* Fix issues related to sne rollover during handshake itself. (Francesco)
* Implement and test prefixlen (David)
* Replace shash with ahash and reuse some of the MD5 code (Dmitry)
* Parse md5+ao options only once in the same function (Dmitry)
* Pass tcp_authopt_info into inbound check path, this avoids second rcu
dereference for same packet.
* Pass tcp_request_socket into inbound check path instead of just listen
socket. This is required for SNE rollover during handshake and clearifies
ISN handling.
* Do not allow disabling via sysctl after enabling once, this is difficult
to support well (David)
* Verbose check for sysctl_tcp_authopt (Dmitry)
* Use netif_index_is_l3_master (David)
* Cleanup ipvx_addr_match (David)
* Add a #define tcp_authopt_needed to wrap static key usage because it looks
nicer.
* Replace rcu_read_lock with rcu_dereference_protected in SNE updates (Eric)
* Remove test suite
Link: https://lore.kernel.org/netdev/[email protected]/

Changes since PATCH v1:
* Implement Sequence Number Extension
* Implement l3index for vrf: TCP_AUTHOPT_KEY_IFINDEX as equivalent of
TCP_MD5SIG_FLAG_IFINDEX
* Expand TCP-AO tests in fcnal-test.sh to near-parity with md5.
* Show addr/port on failure similar to md5
* Remove tox dependency from test suite (create venv directly)
* Switch default pytest output format to TAP (kselftest standard)
* Fix _copy_from_sockptr_tolerant stack corruption on short sockopts.
This was covered in test but error was invisible without STACKPROTECTOR=y
* Fix sysctl_tcp_authopt check in tcp_get_authopt_val before memset. This
was harmless because error code is checked in getsockopt anyway.
* Fix dropping md5 packets on all sockets with AO enabled
* Fix checking (key->recv_id & TCP_AUTHOPT_KEY_ADDR_BIND) instead of
key->flags in tcp_authopt_key_match_exact
* Fix PATCH 1/19 not compiling due to missing "int err" declaration
* Add ratelimited message for AO and MD5 both present
* Export all symbols required by CONFIG_IPV6=m (again)
* Fix compilation with CONFIG_TCP_AUTHOPT=y CONFIG_TCP_MD5SIG=n
* Fix checkpatch issues
* Pass -rrequirements.txt to tox to avoid dependency variation.
Link: https://lore.kernel.org/netdev/[email protected]/

Changes since RFCv3:
* Implement TCP_AUTHOPT handling for timewait and reset replies. Write
tests to execute these paths by injecting packets with scapy
* Handle combining md5 and authopt: if both are configured use authopt.
* Fix locking issues around send_key, introduced in on of the later patches.
* Handle IPv4-mapped-IPv6 addresses: it used to be that an ipv4 SYN sent
to an ipv6 socket with TCP-AO triggered WARN
* Implement un-namespaced sysctl disabled this feature by default
* Allocate new key before removing any old one in setsockopt (Dmitry)
* Remove tcp_authopt_key_info.local_id because it's no longer used (Dmitry)
* Propagate errors from TCP_AUTHOPT getsockopt (Dmitry)
* Fix no-longer-correct TCP_AUTHOPT_KEY_DEL docs (Dmitry)
* Simplify crypto allocation (Eric)
* Use kzmalloc instead of __GFP_ZERO (Eric)
* Add static_key_false tcp_authopt_needed (Eric)
* Clear authopt_info copied from oldsk in __tcp_authopt_openreq (Eric)
* Replace memcmp in ipv4 and ipv6 addr comparisons (Eric)
* Export symbols for CONFIG_IPV6=m (kernel test robot)
* Mark more functions static (kernel test robot)
* Fix build with CONFIG_PROVE_RCU_LIST=y (kernel test robot)
Link: https://lore.kernel.org/netdev/[email protected]/

Changes since RFCv2:
* Removed local_id from ABI and match on send_id/recv_id/addr
* Add all relevant out-of-tree tests to tools/testing/selftests
* Return an error instead of ignoring unknown flags, hopefully this makes
it easier to extend.
* Check sk_family before __tcp_authopt_info_get_or_create in tcp_set_authopt_key
* Use sock_owned_by_me instead of WARN_ON(!lockdep_sock_is_held(sk))
* Fix some intermediate build failures reported by kbuild robot
* Improve documentation
Link: https://lore.kernel.org/netdev/[email protected]/

Changes since RFC:
* Split into per-topic commits for ease of review. The intermediate
commits compile with a few "unused function" warnings and don't do
anything useful by themselves.
* Add ABI documention including kernel-doc on uapi
* Fix lockdep warnings from crypto by creating pools with one shash for
each cpu
* Accept short options to setsockopt by padding with zeros; this
approach allows increasing the size of the structs in the future.
* Support for aes-128-cmac-96
* Support for binding addresses to keys in a way similar to old tcp_md5
* Add support for retrieving received keyid/rnextkeyid and controling
the keyid/rnextkeyid being sent.
Link: https://lore.kernel.org/netdev/01383a8751e97ef826ef2adf93bfde3a08195a43.1626693859.git.cdleonard@gmail.com/

Leonard Crestez (20):
tcp: authopt: Initial support and key management
docs: Add user documentation for tcp_authopt
tcp: authopt: Add crypto initialization
tcp: md5: Refactor tcp_sig_hash_skb_data for AO
tcp: authopt: Compute packet signatures
tcp: authopt: Hook into tcp core
tcp: authopt: Disable via sysctl by default
tcp: authopt: Implement Sequence Number Extension
tcp: ipv6: Add AO signing for tcp_v6_send_response
tcp: authopt: Add support for signing skb-less replies
tcp: ipv4: Add AO signing for skb-less replies
tcp: authopt: Add key selection controls
tcp: authopt: Add initial l3index support
tcp: authopt: Add NOSEND/NORECV flags
tcp: authopt: Add prefixlen support
tcp: authopt: Add /proc/net/tcp_authopt listing all keys
selftests: nettest: Rename md5_prefix to key_addr_prefix
selftests: nettest: Initial tcp_authopt support
selftests: net/fcnal: Initial tcp_authopt support
tcp: authopt: Try to respect rnextkeyid from SYN on SYNACK

Documentation/networking/index.rst | 1 +
Documentation/networking/ip-sysctl.rst | 6 +
Documentation/networking/tcp_authopt.rst | 88 +
include/linux/tcp.h | 15 +
include/net/net_namespace.h | 4 +
include/net/netns/tcp_authopt.h | 12 +
include/net/tcp.h | 27 +-
include/net/tcp_authopt.h | 323 ++++
include/uapi/linux/snmp.h | 1 +
include/uapi/linux/tcp.h | 137 ++
net/ipv4/Kconfig | 14 +
net/ipv4/Makefile | 1 +
net/ipv4/proc.c | 1 +
net/ipv4/sysctl_net_ipv4.c | 39 +
net/ipv4/tcp.c | 68 +-
net/ipv4/tcp_authopt.c | 1847 +++++++++++++++++++++
net/ipv4/tcp_input.c | 53 +-
net/ipv4/tcp_ipv4.c | 138 +-
net/ipv4/tcp_minisocks.c | 12 +
net/ipv4/tcp_output.c | 86 +-
net/ipv6/tcp_ipv6.c | 110 +-
tools/testing/selftests/net/fcnal-test.sh | 329 +++-
tools/testing/selftests/net/nettest.c | 204 ++-
23 files changed, 3430 insertions(+), 86 deletions(-)
create mode 100644 Documentation/networking/tcp_authopt.rst
create mode 100644 include/net/netns/tcp_authopt.h
create mode 100644 include/net/tcp_authopt.h
create mode 100644 net/ipv4/tcp_authopt.c


base-commit: fe8152b38d3a994c4c6fdbc0cd6551d569a5715a
--
2.25.1


2022-01-24 19:08:22

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 03/20] tcp: authopt: Add crypto initialization

The crypto_shash API is used in order to compute packet signatures. The
API comes with several unfortunate limitations:

1) Allocating a crypto_shash can sleep and must be done in user context.
2) Packet signatures must be computed in softirq context
3) Packet signatures use dynamic "traffic keys" which require exclusive
access to crypto_shash for crypto_setkey.

The solution is to allocate one crypto_shash for each possible cpu for
each algorithm at setsockopt time. The per-cpu tfm is then borrowed from
softirq context, signatures are computed and the tfm is returned.

The pool for each algorithm is allocated on first use.

Signed-off-by: Leonard Crestez <[email protected]>
---
include/net/tcp_authopt.h | 16 ++++
net/ipv4/tcp_authopt.c | 195 +++++++++++++++++++++++++++++++++++++-
2 files changed, 210 insertions(+), 1 deletion(-)

diff --git a/include/net/tcp_authopt.h b/include/net/tcp_authopt.h
index 0d9cab459d10..bbd0c0977954 100644
--- a/include/net/tcp_authopt.h
+++ b/include/net/tcp_authopt.h
@@ -4,10 +4,24 @@

#include <uapi/linux/tcp.h>
#include <net/netns/tcp_authopt.h>
#include <linux/tcp.h>

+/* According to RFC5925 the length of the authentication option varies based on
+ * the signature algorithm. Linux only implements the algorithms defined in
+ * RFC5926 which have a constant length of 16.
+ *
+ * This is used in stack allocation of tcp option buffers for output. It is
+ * shorter than the length of the MD5 option.
+ *
+ * Input packets can have authentication options of different lengths but they
+ * will always be flagged as invalid (since no such algorithms are supported).
+ */
+#define TCPOLEN_AUTHOPT_OUTPUT 16
+
+struct tcp_authopt_alg_imp;
+
/**
* struct tcp_authopt_key_info - Representation of a Master Key Tuple as per RFC5925
*
* Key structure lifetime is protected by RCU so send/recv code needs to hold a
* single rcu_read_lock until they're done with the key.
@@ -33,10 +47,12 @@ struct tcp_authopt_key_info {
u8 keylen;
/** @key: Same as &tcp_authopt_key.key */
u8 key[TCP_AUTHOPT_MAXKEYLEN];
/** @addr: Same as &tcp_authopt_key.addr */
struct sockaddr_storage addr;
+ /** @alg: Algorithm implementation matching alg_id */
+ struct tcp_authopt_alg_imp *alg;
};

/**
* struct tcp_authopt_info - Per-socket information regarding tcp_authopt
*
diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index 17392c42e99f..9c853e2e0627 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -2,10 +2,192 @@

#include <net/tcp_authopt.h>
#include <net/ipv6.h>
#include <net/tcp.h>
#include <linux/kref.h>
+#include <crypto/hash.h>
+
+/* All current algorithms have a mac length of 12 but crypto API digestsize can be larger */
+#define TCP_AUTHOPT_MAXMACBUF 20
+#define TCP_AUTHOPT_MAX_TRAFFIC_KEY_LEN 20
+#define TCP_AUTHOPT_MACLEN 12
+
+struct tcp_authopt_alg_pool {
+ struct crypto_ahash *tfm;
+ struct ahash_request *req;
+};
+
+/* Constant data with per-algorithm information from RFC5926
+ * The "KDF" and "MAC" happen to be the same for both algorithms.
+ */
+struct tcp_authopt_alg_imp {
+ /* Name of algorithm in crypto-api */
+ const char *alg_name;
+ /* One of the TCP_AUTHOPT_ALG_* constants from uapi */
+ u8 alg_id;
+ /* Length of traffic key */
+ u8 traffic_key_len;
+
+ /* shared crypto_ahash */
+ struct mutex init_mutex;
+ bool init_done;
+ struct tcp_authopt_alg_pool __percpu *pool;
+};
+
+static struct tcp_authopt_alg_imp tcp_authopt_alg_list[] = {
+ {
+ .alg_id = TCP_AUTHOPT_ALG_HMAC_SHA_1_96,
+ .alg_name = "hmac(sha1)",
+ .traffic_key_len = 20,
+ .init_mutex = __MUTEX_INITIALIZER(tcp_authopt_alg_list[0].init_mutex),
+ },
+ {
+ .alg_id = TCP_AUTHOPT_ALG_AES_128_CMAC_96,
+ .alg_name = "cmac(aes)",
+ .traffic_key_len = 16,
+ .init_mutex = __MUTEX_INITIALIZER(tcp_authopt_alg_list[1].init_mutex),
+ },
+};
+
+/* get a pointer to the tcp_authopt_alg instance or NULL if id invalid */
+static inline struct tcp_authopt_alg_imp *tcp_authopt_alg_get(int alg_num)
+{
+ if (alg_num <= 0 || alg_num > 2)
+ return NULL;
+ return &tcp_authopt_alg_list[alg_num - 1];
+}
+
+static int tcp_authopt_alg_pool_init(struct tcp_authopt_alg_imp *alg,
+ struct tcp_authopt_alg_pool *pool)
+{
+ pool->tfm = crypto_alloc_ahash(alg->alg_name, 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(pool->tfm))
+ return PTR_ERR(pool->tfm);
+
+ pool->req = ahash_request_alloc(pool->tfm, GFP_ATOMIC);
+ if (IS_ERR(pool->req))
+ return PTR_ERR(pool->req);
+ ahash_request_set_callback(pool->req, 0, NULL, NULL);
+
+ return 0;
+}
+
+static void tcp_authopt_alg_pool_free(struct tcp_authopt_alg_pool *pool)
+{
+ if (!IS_ERR_OR_NULL(pool->req))
+ ahash_request_free(pool->req);
+ pool->req = NULL;
+ if (!IS_ERR_OR_NULL(pool->tfm))
+ crypto_free_ahash(pool->tfm);
+ pool->tfm = NULL;
+}
+
+static void __tcp_authopt_alg_free(struct tcp_authopt_alg_imp *alg)
+{
+ int cpu;
+ struct tcp_authopt_alg_pool *pool;
+
+ if (!alg->pool)
+ return;
+ for_each_possible_cpu(cpu) {
+ pool = per_cpu_ptr(alg->pool, cpu);
+ tcp_authopt_alg_pool_free(pool);
+ }
+ free_percpu(alg->pool);
+ alg->pool = NULL;
+}
+
+static int __tcp_authopt_alg_init(struct tcp_authopt_alg_imp *alg)
+{
+ struct tcp_authopt_alg_pool *pool;
+ int cpu;
+ int err;
+
+ BUILD_BUG_ON(TCP_AUTHOPT_MAXMACBUF < TCPOLEN_AUTHOPT_OUTPUT);
+ if (WARN_ON_ONCE(alg->traffic_key_len > TCP_AUTHOPT_MAX_TRAFFIC_KEY_LEN))
+ return -ENOBUFS;
+
+ alg->pool = alloc_percpu(struct tcp_authopt_alg_pool);
+ if (!alg->pool)
+ return -ENOMEM;
+ for_each_possible_cpu(cpu) {
+ pool = per_cpu_ptr(alg->pool, cpu);
+ err = tcp_authopt_alg_pool_init(alg, pool);
+ if (err)
+ goto out_err;
+
+ pool = per_cpu_ptr(alg->pool, cpu);
+ /* sanity checks: */
+ if (WARN_ON_ONCE(crypto_ahash_digestsize(pool->tfm) != alg->traffic_key_len)) {
+ err = -EINVAL;
+ goto out_err;
+ }
+ if (WARN_ON_ONCE(crypto_ahash_digestsize(pool->tfm) > TCP_AUTHOPT_MAXMACBUF)) {
+ err = -EINVAL;
+ goto out_err;
+ }
+ }
+ return 0;
+
+out_err:
+ pr_info("Failed to initialize %s\n", alg->alg_name);
+ __tcp_authopt_alg_free(alg);
+ return err;
+}
+
+static int tcp_authopt_alg_require(struct tcp_authopt_alg_imp *alg)
+{
+ int err = 0;
+
+ mutex_lock(&alg->init_mutex);
+ if (alg->init_done)
+ goto out;
+ err = __tcp_authopt_alg_init(alg);
+ if (err)
+ goto out;
+ pr_info("initialized tcp-ao algorithm %s", alg->alg_name);
+ alg->init_done = true;
+
+out:
+ mutex_unlock(&alg->init_mutex);
+ return err;
+}
+
+static struct tcp_authopt_alg_pool *tcp_authopt_alg_get_pool(struct tcp_authopt_alg_imp *alg)
+{
+ local_bh_disable();
+ return this_cpu_ptr(alg->pool);
+}
+
+static void tcp_authopt_alg_put_pool(struct tcp_authopt_alg_imp *alg,
+ struct tcp_authopt_alg_pool *pool)
+{
+ WARN_ON(pool != this_cpu_ptr(alg->pool));
+ local_bh_enable();
+}
+
+static struct tcp_authopt_alg_pool *tcp_authopt_get_kdf_pool(struct tcp_authopt_key_info *key)
+{
+ return tcp_authopt_alg_get_pool(key->alg);
+}
+
+static void tcp_authopt_put_kdf_pool(struct tcp_authopt_key_info *key,
+ struct tcp_authopt_alg_pool *pool)
+{
+ return tcp_authopt_alg_put_pool(key->alg, pool);
+}
+
+static struct tcp_authopt_alg_pool *tcp_authopt_get_mac_pool(struct tcp_authopt_key_info *key)
+{
+ return tcp_authopt_alg_get_pool(key->alg);
+}
+
+static void tcp_authopt_put_mac_pool(struct tcp_authopt_key_info *key,
+ struct tcp_authopt_alg_pool *pool)
+{
+ return tcp_authopt_alg_put_pool(key->alg, pool);
+}

static inline struct netns_tcp_authopt *sock_net_tcp_authopt(const struct sock *sk)
{
return &sock_net(sk)->tcp_authopt;
}
@@ -49,11 +231,10 @@ void tcp_authopt_clear(struct sock *sk)
if (info) {
tcp_authopt_free(sk, info);
tcp_sk(sk)->authopt_info = NULL;
}
}
-
/* checks that ipv4 or ipv6 addr matches. */
static bool ipvx_addr_match(struct sockaddr_storage *a1,
struct sockaddr_storage *a2)
{
if (a1->ss_family != a2->ss_family)
@@ -206,10 +387,11 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
{
struct tcp_authopt_key opt;
struct tcp_authopt_info *info;
struct tcp_authopt_key_info *key_info, *old_key_info;
struct netns_tcp_authopt *net = sock_net_tcp_authopt(sk);
+ struct tcp_authopt_alg_imp *alg;
int err;

sock_owned_by_me(sk);
if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN))
return -EPERM;
@@ -247,10 +429,20 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
/* Initialize tcp_authopt_info if not already set */
info = __tcp_authopt_info_get_or_create(sk);
if (IS_ERR(info))
return PTR_ERR(info);

+ /* check the algorithm */
+ alg = tcp_authopt_alg_get(opt.alg);
+ if (!alg)
+ return -EINVAL;
+ if (WARN_ON_ONCE(alg->alg_id != opt.alg))
+ return -EINVAL;
+ err = tcp_authopt_alg_require(alg);
+ if (err)
+ return err;
+
key_info = sock_kmalloc(sk, sizeof(*key_info), GFP_KERNEL | __GFP_ZERO);
if (!key_info)
return -ENOMEM;
mutex_lock(&net->mutex);
kref_init(&key_info->ref);
@@ -262,10 +454,11 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
tcp_authopt_key_del(net, old_key_info);
key_info->flags = opt.flags & TCP_AUTHOPT_KEY_KNOWN_FLAGS;
key_info->send_id = opt.send_id;
key_info->recv_id = opt.recv_id;
key_info->alg_id = opt.alg;
+ key_info->alg = alg;
key_info->keylen = opt.keylen;
memcpy(key_info->key, opt.key, opt.keylen);
memcpy(&key_info->addr, &opt.addr, sizeof(key_info->addr));
hlist_add_head_rcu(&key_info->node, &net->head);
mutex_unlock(&net->mutex);
--
2.25.1

2022-01-24 19:08:28

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 04/20] tcp: md5: Refactor tcp_sig_hash_skb_data for AO

This chunk of code is identical between the implementation of TCP-MD5
and TCP-AO so rename and refactor

Signed-off-by: Leonard Crestez <[email protected]>
---
include/net/tcp.h | 2 +-
net/ipv4/tcp.c | 38 ++++++++++++++++++++------------------
net/ipv4/tcp_ipv4.c | 2 +-
net/ipv6/tcp_ipv6.c | 2 +-
4 files changed, 23 insertions(+), 21 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 6cc2eeb45deb..1a0513b0ead0 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1688,11 +1688,11 @@ struct tcp_md5sig_pool *tcp_get_md5sig_pool(void);
static inline void tcp_put_md5sig_pool(void)
{
local_bh_enable();
}

-int tcp_md5_hash_skb_data(struct tcp_md5sig_pool *, const struct sk_buff *,
+int tcp_sig_hash_skb_data(struct ahash_request *, const struct sk_buff *,
unsigned int header_len);
int tcp_md5_hash_key(struct tcp_md5sig_pool *hp,
const struct tcp_md5sig_key *key);

/* From tcp_fastopen.c */
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 3651a1e13a16..f00f67d1f2b0 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -4399,16 +4399,31 @@ struct tcp_md5sig_pool *tcp_get_md5sig_pool(void)
local_bh_enable();
return NULL;
}
EXPORT_SYMBOL(tcp_get_md5sig_pool);

-int tcp_md5_hash_skb_data(struct tcp_md5sig_pool *hp,
+int tcp_md5_hash_key(struct tcp_md5sig_pool *hp, const struct tcp_md5sig_key *key)
+{
+ u8 keylen = READ_ONCE(key->keylen); /* paired with WRITE_ONCE() in tcp_md5_do_add */
+ struct scatterlist sg;
+
+ sg_init_one(&sg, key->key, keylen);
+ ahash_request_set_crypt(hp->md5_req, &sg, NULL, keylen);
+
+ /* We use data_race() because tcp_md5_do_add() might change key->key under us */
+ return data_race(crypto_ahash_update(hp->md5_req));
+}
+EXPORT_SYMBOL(tcp_md5_hash_key);
+#endif /* CONFIG_TCP_MD5SIG */
+
+#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AUTHOPT)
+
+int tcp_sig_hash_skb_data(struct ahash_request *req,
const struct sk_buff *skb, unsigned int header_len)
{
struct scatterlist sg;
const struct tcphdr *tp = tcp_hdr(skb);
- struct ahash_request *req = hp->md5_req;
unsigned int i;
const unsigned int head_data_len = skb_headlen(skb) > header_len ?
skb_headlen(skb) - header_len : 0;
const struct skb_shared_info *shi = skb_shinfo(skb);
struct sk_buff *frag_iter;
@@ -4431,31 +4446,18 @@ int tcp_md5_hash_skb_data(struct tcp_md5sig_pool *hp,
if (crypto_ahash_update(req))
return 1;
}

skb_walk_frags(skb, frag_iter)
- if (tcp_md5_hash_skb_data(hp, frag_iter, 0))
+ if (tcp_sig_hash_skb_data(req, frag_iter, 0))
return 1;

return 0;
}
-EXPORT_SYMBOL(tcp_md5_hash_skb_data);
+EXPORT_SYMBOL(tcp_sig_hash_skb_data);

-int tcp_md5_hash_key(struct tcp_md5sig_pool *hp, const struct tcp_md5sig_key *key)
-{
- u8 keylen = READ_ONCE(key->keylen); /* paired with WRITE_ONCE() in tcp_md5_do_add */
- struct scatterlist sg;
-
- sg_init_one(&sg, key->key, keylen);
- ahash_request_set_crypt(hp->md5_req, &sg, NULL, keylen);
-
- /* We use data_race() because tcp_md5_do_add() might change key->key under us */
- return data_race(crypto_ahash_update(hp->md5_req));
-}
-EXPORT_SYMBOL(tcp_md5_hash_key);
-
-#endif
+#endif /* defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AUTHOPT) */

void tcp_done(struct sock *sk)
{
struct request_sock *req;

diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index f03d48e574c9..442c28e1c72a 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1381,11 +1381,11 @@ int tcp_v4_md5_hash_skb(char *md5_hash, const struct tcp_md5sig_key *key,
if (crypto_ahash_init(req))
goto clear_hash;

if (tcp_v4_md5_hash_headers(hp, daddr, saddr, th, skb->len))
goto clear_hash;
- if (tcp_md5_hash_skb_data(hp, skb, th->doff << 2))
+ if (tcp_sig_hash_skb_data(hp->md5_req, skb, th->doff << 2))
goto clear_hash;
if (tcp_md5_hash_key(hp, key))
goto clear_hash;
ahash_request_set_crypt(req, NULL, md5_hash, 0);
if (crypto_ahash_final(req))
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index 075ee8a2df3b..7aed4cdfcd65 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -750,11 +750,11 @@ static int tcp_v6_md5_hash_skb(char *md5_hash,
if (crypto_ahash_init(req))
goto clear_hash;

if (tcp_v6_md5_hash_headers(hp, daddr, saddr, th, skb->len))
goto clear_hash;
- if (tcp_md5_hash_skb_data(hp, skb, th->doff << 2))
+ if (tcp_sig_hash_skb_data(hp->md5_req, skb, th->doff << 2))
goto clear_hash;
if (tcp_md5_hash_key(hp, key))
goto clear_hash;
ahash_request_set_crypt(req, NULL, md5_hash, 0);
if (crypto_ahash_final(req))
--
2.25.1

2022-01-24 19:08:33

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 05/20] tcp: authopt: Compute packet signatures

Computing tcp authopt packet signatures is a two step process:

* traffic key is computed based on tcp 4-tuple, initial sequence numbers
and the secret key.
* packet mac is computed based on traffic key and content of individual
packets.

The traffic key could be cached for established sockets but it is not.

A single code path exists for ipv4/ipv6 and input/output. This keeps the
code short but slightly slower due to lots of conditionals.

On output we read remote IP address from socket members on output, we
can't use skb network header because it's computed after TCP options.

On input we read remote IP address from skb network headers, we can't
use socket binding members because those are not available for SYN.

Signed-off-by: Leonard Crestez <[email protected]>
---
include/net/tcp_authopt.h | 9 +
net/ipv4/tcp_authopt.c | 453 ++++++++++++++++++++++++++++++++++++++
2 files changed, 462 insertions(+)

diff --git a/include/net/tcp_authopt.h b/include/net/tcp_authopt.h
index bbd0c0977954..7d0a66fcde71 100644
--- a/include/net/tcp_authopt.h
+++ b/include/net/tcp_authopt.h
@@ -68,10 +68,19 @@ struct tcp_authopt_info {
u32 src_isn;
/** @dst_isn: Remote Initial Sequence Number */
u32 dst_isn;
};

+/* TCP authopt as found in header */
+struct tcphdr_authopt {
+ u8 num;
+ u8 len;
+ u8 keyid;
+ u8 rnextkeyid;
+ u8 mac[0];
+};
+
#ifdef CONFIG_TCP_AUTHOPT
void tcp_authopt_clear(struct sock *sk);
int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen);
int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *key);
int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen);
diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index 9c853e2e0627..06727b4f96e8 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -464,10 +464,463 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
mutex_unlock(&net->mutex);

return 0;
}

+static int tcp_authopt_get_isn(struct sock *sk,
+ struct tcp_authopt_info *info,
+ struct sk_buff *skb,
+ int input,
+ __be32 *sisn,
+ __be32 *disn)
+{
+ struct tcphdr *th = tcp_hdr(skb);
+
+ /* Special cases for SYN and SYN/ACK */
+ if (th->syn && !th->ack) {
+ *sisn = th->seq;
+ *disn = 0;
+ return 0;
+ }
+ if (th->syn && th->ack) {
+ *sisn = th->seq;
+ *disn = htonl(ntohl(th->ack_seq) - 1);
+ return 0;
+ }
+
+ if (sk->sk_state == TCP_NEW_SYN_RECV) {
+ struct tcp_request_sock *rsk = (struct tcp_request_sock *)sk;
+
+ if (WARN_ONCE(!input, "Caller passed wrong socket"))
+ return -EINVAL;
+ *sisn = htonl(rsk->rcv_isn);
+ *disn = htonl(rsk->snt_isn);
+ return 0;
+ } else if (sk->sk_state == TCP_LISTEN) {
+ /* Signature computation for non-syn packet on a listen
+ * socket is not possible because we lack the initial
+ * sequence numbers.
+ *
+ * Input segments that are not matched by any request,
+ * established or timewait socket will get here. These
+ * are not normally sent by peers.
+ *
+ * Their signature might be valid but we don't have
+ * enough state to determine that. TCP-MD5 can attempt
+ * to validate and reply with a signed RST because it
+ * doesn't care about ISNs.
+ *
+ * Reporting an error from signature code causes the
+ * packet to be discarded which is good.
+ */
+ if (WARN_ONCE(!input, "Caller passed wrong socket"))
+ return -EINVAL;
+ *sisn = 0;
+ *disn = 0;
+ return 0;
+ }
+ if (WARN_ONCE(!info, "caller did not pass tcp_authopt_info\n"))
+ return -EINVAL;
+ /* Initial sequence numbers for ESTABLISHED connections from info */
+ if (input) {
+ *sisn = htonl(info->dst_isn);
+ *disn = htonl(info->src_isn);
+ } else {
+ *sisn = htonl(info->src_isn);
+ *disn = htonl(info->dst_isn);
+ }
+ return 0;
+}
+
+/* Feed one buffer into ahash
+ * The buffer is assumed to be DMA-able
+ */
+static int crypto_ahash_buf(struct ahash_request *req, u8 *buf, uint len)
+{
+ struct scatterlist sg;
+
+ sg_init_one(&sg, buf, len);
+ ahash_request_set_crypt(req, &sg, NULL, len);
+
+ return crypto_ahash_update(req);
+}
+
+/* feed traffic key into ahash */
+static int tcp_authopt_ahash_traffic_key(struct tcp_authopt_alg_pool *pool,
+ struct sock *sk,
+ struct sk_buff *skb,
+ struct tcp_authopt_info *info,
+ bool input,
+ bool ipv6)
+{
+ struct tcphdr *th = tcp_hdr(skb);
+ int err;
+ __be32 sisn, disn;
+ __be16 digestbits = htons(crypto_ahash_digestsize(pool->tfm) * 8);
+ /* For ahash const data buffers don't work so ensure header is on stack */
+ char traffic_key_context_header[7] = "\x01TCP-AO";
+
+ // RFC5926 section 3.1.1.1
+ err = crypto_ahash_buf(pool->req, traffic_key_context_header, 7);
+ if (err)
+ return err;
+
+ /* Addresses from packet on input and from sk_common on output
+ * This is because on output MAC is computed before prepending IP header
+ */
+ if (input) {
+ if (ipv6)
+ err = crypto_ahash_buf(pool->req, (u8 *)&ipv6_hdr(skb)->saddr, 32);
+ else
+ err = crypto_ahash_buf(pool->req, (u8 *)&ip_hdr(skb)->saddr, 8);
+ if (err)
+ return err;
+ } else {
+ if (ipv6) {
+#if IS_ENABLED(CONFIG_IPV6)
+ err = crypto_ahash_buf(pool->req, (u8 *)&sk->sk_v6_rcv_saddr, 16);
+ if (err)
+ return err;
+ err = crypto_ahash_buf(pool->req, (u8 *)&sk->sk_v6_daddr, 16);
+ if (err)
+ return err;
+#else
+ return -EINVAL;
+#endif
+ } else {
+ err = crypto_ahash_buf(pool->req, (u8 *)&sk->sk_rcv_saddr, 4);
+ if (err)
+ return err;
+ err = crypto_ahash_buf(pool->req, (u8 *)&sk->sk_daddr, 4);
+ if (err)
+ return err;
+ }
+ }
+
+ /* TCP ports from header */
+ err = crypto_ahash_buf(pool->req, (u8 *)&th->source, 4);
+ if (err)
+ return err;
+ err = tcp_authopt_get_isn(sk, info, skb, input, &sisn, &disn);
+ if (err)
+ return err;
+ err = crypto_ahash_buf(pool->req, (u8 *)&sisn, 4);
+ if (err)
+ return err;
+ err = crypto_ahash_buf(pool->req, (u8 *)&disn, 4);
+ if (err)
+ return err;
+ err = crypto_ahash_buf(pool->req, (u8 *)&digestbits, 2);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+/* Convert a variable-length key to a 16-byte fixed-length key for AES-CMAC
+ * This is described in RFC5926 section 3.1.1.2
+ */
+static int aes_setkey_derived(struct crypto_ahash *tfm, struct ahash_request *req,
+ u8 *key, size_t keylen)
+{
+ static const u8 zeros[16] = {0};
+ struct scatterlist sg;
+ u8 derived_key[16];
+ int err;
+
+ if (WARN_ON_ONCE(crypto_ahash_digestsize(tfm) != sizeof(derived_key)))
+ return -EINVAL;
+ err = crypto_ahash_setkey(tfm, zeros, sizeof(zeros));
+ if (err)
+ return err;
+ err = crypto_ahash_init(req);
+ if (err)
+ return err;
+ sg_init_one(&sg, key, keylen);
+ ahash_request_set_crypt(req, &sg, derived_key, keylen);
+ err = crypto_ahash_digest(req);
+ if (err)
+ return err;
+ return crypto_ahash_setkey(tfm, derived_key, sizeof(derived_key));
+}
+
+static int tcp_authopt_setkey(struct tcp_authopt_alg_pool *pool, struct tcp_authopt_key_info *key)
+{
+ if (key->alg_id == TCP_AUTHOPT_ALG_AES_128_CMAC_96 && key->keylen != 16)
+ return aes_setkey_derived(pool->tfm, pool->req, key->key, key->keylen);
+ else
+ return crypto_ahash_setkey(pool->tfm, key->key, key->keylen);
+}
+
+static int tcp_authopt_get_traffic_key(struct sock *sk,
+ struct sk_buff *skb,
+ struct tcp_authopt_key_info *key,
+ struct tcp_authopt_info *info,
+ bool input,
+ bool ipv6,
+ u8 *traffic_key)
+{
+ struct tcp_authopt_alg_pool *pool;
+ int err;
+
+ pool = tcp_authopt_get_kdf_pool(key);
+ if (IS_ERR(pool))
+ return PTR_ERR(pool);
+
+ err = tcp_authopt_setkey(pool, key);
+ if (err)
+ goto out;
+ err = crypto_ahash_init(pool->req);
+ if (err)
+ goto out;
+
+ err = tcp_authopt_ahash_traffic_key(pool, sk, skb, info, input, ipv6);
+ if (err)
+ goto out;
+
+ ahash_request_set_crypt(pool->req, NULL, traffic_key, 0);
+ err = crypto_ahash_final(pool->req);
+ if (err)
+ return err;
+
+out:
+ tcp_authopt_put_kdf_pool(key, pool);
+ return err;
+}
+
+static int crypto_ahash_buf_zero(struct ahash_request *req, int len)
+{
+ u8 zeros[TCP_AUTHOPT_MACLEN] = {0};
+ int buflen, err;
+
+ /* In practice this is always called with len exactly 12.
+ * Even on input we drop unusual signature sizes early.
+ */
+ while (len) {
+ buflen = min_t(int, len, sizeof(zeros));
+ err = crypto_ahash_buf(req, zeros, buflen);
+ if (err)
+ return err;
+ len -= buflen;
+ }
+
+ return 0;
+}
+
+static int tcp_authopt_hash_tcp4_pseudoheader(struct tcp_authopt_alg_pool *pool,
+ __be32 saddr,
+ __be32 daddr,
+ int nbytes)
+{
+ struct tcp4_pseudohdr phdr = {
+ .saddr = saddr,
+ .daddr = daddr,
+ .pad = 0,
+ .protocol = IPPROTO_TCP,
+ .len = htons(nbytes)
+ };
+ return crypto_ahash_buf(pool->req, (u8 *)&phdr, sizeof(phdr));
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static int tcp_authopt_hash_tcp6_pseudoheader(struct tcp_authopt_alg_pool *pool,
+ struct in6_addr *saddr,
+ struct in6_addr *daddr,
+ u32 plen)
+{
+ int err;
+ __be32 buf[2];
+
+ buf[0] = htonl(plen);
+ buf[1] = htonl(IPPROTO_TCP);
+
+ err = crypto_ahash_buf(pool->req, (u8 *)saddr, sizeof(*saddr));
+ if (err)
+ return err;
+ err = crypto_ahash_buf(pool->req, (u8 *)daddr, sizeof(*daddr));
+ if (err)
+ return err;
+ return crypto_ahash_buf(pool->req, (u8 *)&buf, sizeof(buf));
+}
+#endif
+
+/** Hash tcphdr options.
+ *
+ * If include_options is false then only the TCPOPT_AUTHOPT option itself is hashed
+ * Point to AO inside TH is passed by the caller
+ */
+static int tcp_authopt_hash_opts(struct tcp_authopt_alg_pool *pool,
+ struct tcphdr *th,
+ struct tcphdr_authopt *aoptr,
+ bool include_options)
+{
+ int err;
+ /* start of options */
+ u8 *tcp_opts = (u8 *)(th + 1);
+ /* start of options */
+ u8 *aobuf = (u8 *)aoptr;
+ u8 aolen = aoptr->len;
+
+ if (WARN_ONCE(aoptr->num != TCPOPT_AUTHOPT, "Bad aoptr\n"))
+ return -EINVAL;
+
+ if (include_options) {
+ /* end of options */
+ u8 *tcp_data = ((u8 *)th) + th->doff * 4;
+
+ err = crypto_ahash_buf(pool->req, tcp_opts, aobuf - tcp_opts + 4);
+ if (err)
+ return err;
+ err = crypto_ahash_buf_zero(pool->req, aolen - 4);
+ if (err)
+ return err;
+ err = crypto_ahash_buf(pool->req, aobuf + aolen, tcp_data - (aobuf + aolen));
+ if (err)
+ return err;
+ } else {
+ err = crypto_ahash_buf(pool->req, aobuf, 4);
+ if (err)
+ return err;
+ err = crypto_ahash_buf_zero(pool->req, aolen - 4);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int tcp_authopt_hash_packet(struct tcp_authopt_alg_pool *pool,
+ struct sock *sk,
+ struct sk_buff *skb,
+ struct tcphdr_authopt *aoptr,
+ bool input,
+ bool ipv6,
+ bool include_options,
+ u8 *macbuf)
+{
+ struct tcphdr *th = tcp_hdr(skb);
+ int err;
+
+ /* NOTE: SNE unimplemented */
+ __be32 sne = 0;
+
+ err = crypto_ahash_init(pool->req);
+ if (err)
+ return err;
+
+ err = crypto_ahash_buf(pool->req, (u8 *)&sne, 4);
+ if (err)
+ return err;
+
+ if (ipv6) {
+#if IS_ENABLED(CONFIG_IPV6)
+ struct in6_addr *saddr;
+ struct in6_addr *daddr;
+
+ if (input) {
+ saddr = &ipv6_hdr(skb)->saddr;
+ daddr = &ipv6_hdr(skb)->daddr;
+ } else {
+ saddr = &sk->sk_v6_rcv_saddr;
+ daddr = &sk->sk_v6_daddr;
+ }
+ err = tcp_authopt_hash_tcp6_pseudoheader(pool, saddr, daddr, skb->len);
+ if (err)
+ return err;
+#else
+ return -EINVAL;
+#endif
+ } else {
+ __be32 saddr;
+ __be32 daddr;
+
+ if (input) {
+ saddr = ip_hdr(skb)->saddr;
+ daddr = ip_hdr(skb)->daddr;
+ } else {
+ saddr = sk->sk_rcv_saddr;
+ daddr = sk->sk_daddr;
+ }
+ err = tcp_authopt_hash_tcp4_pseudoheader(pool, saddr, daddr, skb->len);
+ if (err)
+ return err;
+ }
+
+ // TCP header with checksum set to zero
+ {
+ struct tcphdr hashed_th = *th;
+
+ hashed_th.check = 0;
+ err = crypto_ahash_buf(pool->req, (u8 *)&hashed_th, sizeof(hashed_th));
+ if (err)
+ return err;
+ }
+
+ // TCP options
+ err = tcp_authopt_hash_opts(pool, th, aoptr, include_options);
+ if (err)
+ return err;
+
+ // Rest of SKB->data
+ err = tcp_sig_hash_skb_data(pool->req, skb, th->doff << 2);
+ if (err)
+ return err;
+
+ ahash_request_set_crypt(pool->req, NULL, macbuf, 0);
+ return crypto_ahash_final(pool->req);
+}
+
+/* __tcp_authopt_calc_mac - Compute packet MAC using key
+ *
+ * The macbuf output buffer must be large enough to fit the digestsize of the
+ * underlying transform before truncation.
+ * This means TCP_AUTHOPT_MAXMACBUF, not TCP_AUTHOPT_MACLEN
+ */
+static int __tcp_authopt_calc_mac(struct sock *sk,
+ struct sk_buff *skb,
+ struct tcphdr_authopt *aoptr,
+ struct tcp_authopt_key_info *key,
+ struct tcp_authopt_info *info,
+ bool input,
+ char *macbuf)
+{
+ struct tcp_authopt_alg_pool *mac_pool;
+ u8 traffic_key[TCP_AUTHOPT_MAX_TRAFFIC_KEY_LEN];
+ int err;
+ bool ipv6 = (sk->sk_family != AF_INET);
+
+ if (sk->sk_family != AF_INET && sk->sk_family != AF_INET6)
+ return -EINVAL;
+
+ err = tcp_authopt_get_traffic_key(sk, skb, key, info, input, ipv6, traffic_key);
+ if (err)
+ return err;
+
+ mac_pool = tcp_authopt_get_mac_pool(key);
+ if (IS_ERR(mac_pool))
+ return PTR_ERR(mac_pool);
+ err = crypto_ahash_setkey(mac_pool->tfm, traffic_key, key->alg->traffic_key_len);
+ if (err)
+ goto out;
+ err = crypto_ahash_init(mac_pool->req);
+ if (err)
+ return err;
+
+ err = tcp_authopt_hash_packet(mac_pool,
+ sk,
+ skb,
+ aoptr,
+ input,
+ ipv6,
+ !(key->flags & TCP_AUTHOPT_KEY_EXCLUDE_OPTS),
+ macbuf);
+
+out:
+ tcp_authopt_put_mac_pool(key, mac_pool);
+ return err;
+}
+
static int tcp_authopt_init_net(struct net *full_net)
{
struct netns_tcp_authopt *net = &full_net->tcp_authopt;

mutex_init(&net->mutex);
--
2.25.1

2022-01-24 19:08:36

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 09/20] tcp: ipv6: Add AO signing for tcp_v6_send_response

This is a special code path for acks and resets outside of normal
connection establishment and closing.

Signed-off-by: Leonard Crestez <[email protected]>
---
net/ipv4/tcp_authopt.c | 2 ++
net/ipv6/tcp_ipv6.c | 59 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+)

diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index 5e240b4ada60..3c05b6c55191 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -374,10 +374,11 @@ struct tcp_authopt_key_info *__tcp_authopt_select_key(const struct sock *sk,
{
struct netns_tcp_authopt *net = sock_net_tcp_authopt(sk);

return tcp_authopt_lookup_send(net, addr_sk, -1);
}
+EXPORT_SYMBOL(__tcp_authopt_select_key);

static struct tcp_authopt_info *__tcp_authopt_info_get_or_create(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_authopt_info *info;
@@ -1199,10 +1200,11 @@ int tcp_authopt_hash(char *hash_location,
* try to make it obvious inside the packet.
*/
memset(hash_location, 0, TCP_AUTHOPT_MACLEN);
return err;
}
+EXPORT_SYMBOL(tcp_authopt_hash);

/**
* tcp_authopt_lookup_recv - lookup key for receive
*
* @sk: Receive socket
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index 01c1c065decc..b72d57565c18 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -886,10 +886,48 @@ const struct tcp_request_sock_ops tcp_request_sock_ipv6_ops = {
.init_seq = tcp_v6_init_seq,
.init_ts_off = tcp_v6_init_ts_off,
.send_synack = tcp_v6_send_synack,
};

+#ifdef CONFIG_TCP_AUTHOPT
+static int tcp_v6_send_response_init_authopt(const struct sock *sk,
+ struct tcp_authopt_info **info,
+ struct tcp_authopt_key_info **key,
+ u8 *rnextkeyid)
+{
+ /* Key lookup before SKB allocation */
+ if (!(tcp_authopt_needed && sk))
+ return 0;
+ if (sk->sk_state == TCP_TIME_WAIT)
+ *info = tcp_twsk(sk)->tw_authopt_info;
+ else
+ *info = rcu_dereference(tcp_sk(sk)->authopt_info);
+ if (!*info)
+ return 0;
+ *key = __tcp_authopt_select_key(sk, *info, sk, rnextkeyid);
+ if (*key)
+ return TCPOLEN_AUTHOPT_OUTPUT;
+ return 0;
+}
+
+static void tcp_v6_send_response_sign_authopt(const struct sock *sk,
+ struct tcp_authopt_info *info,
+ struct tcp_authopt_key_info *key,
+ struct sk_buff *skb,
+ struct tcphdr_authopt *ptr,
+ u8 rnextkeyid)
+{
+ if (!(tcp_authopt_needed && key))
+ return;
+ ptr->num = TCPOPT_AUTHOPT;
+ ptr->len = TCPOLEN_AUTHOPT_OUTPUT;
+ ptr->keyid = key->send_id;
+ ptr->rnextkeyid = rnextkeyid;
+ tcp_authopt_hash(ptr->mac, key, info, (struct sock *)sk, skb);
+}
+#endif
+
static void tcp_v6_send_response(const struct sock *sk, struct sk_buff *skb, u32 seq,
u32 ack, u32 win, u32 tsval, u32 tsecr,
int oif, struct tcp_md5sig_key *key, int rst,
u8 tclass, __be32 label, u32 priority)
{
@@ -901,13 +939,30 @@ static void tcp_v6_send_response(const struct sock *sk, struct sk_buff *skb, u32
struct sock *ctl_sk = net->ipv6.tcp_sk;
unsigned int tot_len = sizeof(struct tcphdr);
__be32 mrst = 0, *topt;
struct dst_entry *dst;
__u32 mark = 0;
+#ifdef CONFIG_TCP_AUTHOPT
+ struct tcp_authopt_info *aoinfo;
+ struct tcp_authopt_key_info *aokey;
+ u8 aornextkeyid;
+ int aolen;
+#endif

if (tsecr)
tot_len += TCPOLEN_TSTAMP_ALIGNED;
+#ifdef CONFIG_TCP_AUTHOPT
+ /* Key lookup before SKB allocation */
+ aolen = tcp_v6_send_response_init_authopt(sk, &aoinfo, &aokey, &aornextkeyid);
+ if (aolen) {
+ tot_len += aolen;
+#ifdef CONFIG_TCP_MD5SIG
+ /* Don't use MD5 */
+ key = NULL;
+#endif
+ }
+#endif
#ifdef CONFIG_TCP_MD5SIG
if (key)
tot_len += TCPOLEN_MD5SIG_ALIGNED;
#endif

@@ -960,10 +1015,14 @@ static void tcp_v6_send_response(const struct sock *sk, struct sk_buff *skb, u32
tcp_v6_md5_hash_hdr((__u8 *)topt, key,
&ipv6_hdr(skb)->saddr,
&ipv6_hdr(skb)->daddr, t1);
}
#endif
+#ifdef CONFIG_TCP_AUTHOPT
+ tcp_v6_send_response_sign_authopt(sk, aoinfo, aokey, buff,
+ (struct tcphdr_authopt *)topt, aornextkeyid);
+#endif

memset(&fl6, 0, sizeof(fl6));
fl6.daddr = ipv6_hdr(skb)->saddr;
fl6.saddr = ipv6_hdr(skb)->daddr;
fl6.flowlabel = label;
--
2.25.1

2022-01-24 19:08:37

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 07/20] tcp: authopt: Disable via sysctl by default

This is mainly intended to protect against local privilege escalations
through a rarely used feature so it is deliberately not namespaced.

Enforcement is only at the setsockopt level, this should be enough to
ensure that the tcp_authopt_needed static key never turns on.

No effort is made to handle disabling when the feature is already in
use.

Signed-off-by: Leonard Crestez <[email protected]>
---
Documentation/networking/ip-sysctl.rst | 6 ++++
include/net/tcp_authopt.h | 1 +
net/ipv4/sysctl_net_ipv4.c | 39 ++++++++++++++++++++++++++
net/ipv4/tcp_authopt.c | 27 +++++++++++++++++-
4 files changed, 72 insertions(+), 1 deletion(-)

diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst
index 2572eecc3e86..fb732f134ddd 100644
--- a/Documentation/networking/ip-sysctl.rst
+++ b/Documentation/networking/ip-sysctl.rst
@@ -989,10 +989,16 @@ tcp_limit_output_bytes - INTEGER
tcp_challenge_ack_limit - INTEGER
Limits number of Challenge ACK sent per second, as recommended
in RFC 5961 (Improving TCP's Robustness to Blind In-Window Attacks)
Default: 1000

+tcp_authopt - BOOLEAN
+ Enable the TCP Authentication Option (RFC5925), a replacement for TCP
+ MD5 Signatures (RFC2835).
+
+ Default: 0
+
UDP variables
=============

udp_l3mdev_accept - BOOLEAN
Enabling this option allows a "global" bound socket to work
diff --git a/include/net/tcp_authopt.h b/include/net/tcp_authopt.h
index 7096e3ad59a6..4c9ec1f39932 100644
--- a/include/net/tcp_authopt.h
+++ b/include/net/tcp_authopt.h
@@ -80,10 +80,11 @@ struct tcphdr_authopt {
};

#ifdef CONFIG_TCP_AUTHOPT
DECLARE_STATIC_KEY_FALSE(tcp_authopt_needed_key);
#define tcp_authopt_needed (static_branch_unlikely(&tcp_authopt_needed_key))
+extern int sysctl_tcp_authopt;

void tcp_authopt_free(struct sock *sk, struct tcp_authopt_info *info);
void tcp_authopt_clear(struct sock *sk);
int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen);
int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *key);
diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c
index 97eb54774924..07de2666314c 100644
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -17,10 +17,11 @@
#include <net/udp.h>
#include <net/cipso_ipv4.h>
#include <net/ping.h>
#include <net/protocol.h>
#include <net/netevent.h>
+#include <net/tcp_authopt.h>

static int two = 2;
static int three __maybe_unused = 3;
static int four = 4;
static int thousand = 1000;
@@ -472,10 +473,37 @@ static int proc_fib_multipath_hash_fields(struct ctl_table *table, int write,

return ret;
}
#endif

+#ifdef CONFIG_TCP_AUTHOPT
+static int proc_tcp_authopt(struct ctl_table *ctl,
+ int write, void *buffer, size_t *lenp,
+ loff_t *ppos)
+{
+ int val = sysctl_tcp_authopt;
+ struct ctl_table tmp = {
+ .data = &val,
+ .mode = ctl->mode,
+ .maxlen = sizeof(val),
+ .extra1 = SYSCTL_ZERO,
+ .extra2 = SYSCTL_ONE,
+ };
+ int err;
+
+ err = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
+ if (err)
+ return err;
+ if (sysctl_tcp_authopt && !val) {
+ net_warn_ratelimited("Enabling TCP Authentication Option is permanent\n");
+ return -EINVAL;
+ }
+ sysctl_tcp_authopt = val;
+ return 0;
+}
+#endif
+
static struct ctl_table ipv4_table[] = {
{
.procname = "tcp_max_orphans",
.data = &sysctl_tcp_max_orphans,
.maxlen = sizeof(int),
@@ -583,10 +611,21 @@ static struct ctl_table ipv4_table[] = {
.mode = 0644,
.proc_handler = proc_douintvec_minmax,
.extra1 = &sysctl_fib_sync_mem_min,
.extra2 = &sysctl_fib_sync_mem_max,
},
+#ifdef CONFIG_TCP_AUTHOPT
+ {
+ .procname = "tcp_authopt",
+ .data = &sysctl_tcp_authopt,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_tcp_authopt,
+ .extra1 = SYSCTL_ZERO,
+ .extra2 = SYSCTL_ONE,
+ },
+#endif
{ }
};

static struct ctl_table ipv4_net_table[] = {
{
diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index 694dbc9f3a94..939dfb4a6f12 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -4,10 +4,15 @@
#include <net/ipv6.h>
#include <net/tcp.h>
#include <linux/kref.h>
#include <crypto/hash.h>

+/* This is mainly intended to protect against local privilege escalations through
+ * a rarely used feature so it is deliberately not namespaced.
+ */
+int sysctl_tcp_authopt;
+
/* This is enabled when first struct tcp_authopt_info is allocated and never released */
DEFINE_STATIC_KEY_FALSE(tcp_authopt_needed_key);
EXPORT_SYMBOL(tcp_authopt_needed_key);

/* All current algorithms have a mac length of 12 but crypto API digestsize can be larger */
@@ -430,17 +435,30 @@ static int _copy_from_sockptr_tolerant(u8 *dst,
memset(dst + srclen, 0, dstlen - srclen);

return err;
}

+static int check_sysctl_tcp_authopt(void)
+{
+ if (!sysctl_tcp_authopt) {
+ net_warn_ratelimited("TCP Authentication Option disabled by sysctl.\n");
+ return -EPERM;
+ }
+
+ return 0;
+}
+
int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen)
{
struct tcp_authopt opt;
struct tcp_authopt_info *info;
int err;

sock_owned_by_me(sk);
+ err = check_sysctl_tcp_authopt();
+ if (err)
+ return err;

err = _copy_from_sockptr_tolerant((u8 *)&opt, sizeof(opt), optval, optlen);
if (err)
return err;

@@ -458,14 +476,18 @@ int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen)

int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt)
{
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_authopt_info *info;
+ int err;

+ memset(opt, 0, sizeof(*opt));
sock_owned_by_me(sk);
+ err = check_sysctl_tcp_authopt();
+ if (err)
+ return err;

- memset(opt, 0, sizeof(*opt));
info = rcu_dereference_check(tp->authopt_info, lockdep_sock_is_held(sk));
if (!info)
return -ENOENT;

opt->flags = info->flags & TCP_AUTHOPT_KNOWN_FLAGS;
@@ -486,10 +508,13 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
struct netns_tcp_authopt *net = sock_net_tcp_authopt(sk);
struct tcp_authopt_alg_imp *alg;
int err;

sock_owned_by_me(sk);
+ err = check_sysctl_tcp_authopt();
+ if (err)
+ return err;
if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN))
return -EPERM;

err = _copy_from_sockptr_tolerant((u8 *)&opt, sizeof(opt), optval, optlen);
if (err)
--
2.25.1

2022-01-24 19:08:42

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 08/20] tcp: authopt: Implement Sequence Number Extension

Add a compute_sne function which finds the value of SNE for a certain
SEQ given an already known "recent" SNE/SEQ. This is implemented using
the standard tcp before/after macro and will work for SEQ values that
are without 2^31 of the SEQ for which we know the SNE.

For updating we advance the value for rcv_sne at the same time as
rcv_nxt and for snd_sne at the same time as snd_nxt. We could track
other values (for example snd_una) but this is good enough and works
very easily for timewait socket.

This implementation is different from RFC suggestions and doesn't
require additional flags. It does pass tests from this draft:
https://datatracker.ietf.org/doc/draft-touch-sne/

Signed-off-by: Leonard Crestez <[email protected]>
---
include/net/tcp_authopt.h | 34 ++++++++++++++
net/ipv4/tcp_authopt.c | 98 ++++++++++++++++++++++++++++++++++++++-
net/ipv4/tcp_input.c | 1 +
net/ipv4/tcp_output.c | 1 +
4 files changed, 132 insertions(+), 2 deletions(-)

diff --git a/include/net/tcp_authopt.h b/include/net/tcp_authopt.h
index 4c9ec1f39932..6e9b5ca22f62 100644
--- a/include/net/tcp_authopt.h
+++ b/include/net/tcp_authopt.h
@@ -66,10 +66,14 @@ struct tcp_authopt_info {
u32 flags;
/** @src_isn: Local Initial Sequence Number */
u32 src_isn;
/** @dst_isn: Remote Initial Sequence Number */
u32 dst_isn;
+ /** @rcv_sne: Recv-side Sequence Number Extension tracking tcp_sock.rcv_nxt */
+ u32 rcv_sne;
+ /** @snd_sne: Send-side Sequence Number Extension tracking tcp_sock.snd_nxt */
+ u32 snd_sne;
};

/* TCP authopt as found in header */
struct tcphdr_authopt {
u8 num;
@@ -185,10 +189,34 @@ static inline int tcp_authopt_inbound_check_req(struct request_sock *req, struct
if (info)
return __tcp_authopt_inbound_check((struct sock *)req, skb, info, opt);
}
return 0;
}
+void __tcp_authopt_update_rcv_sne(struct tcp_sock *tp, struct tcp_authopt_info *info, u32 seq);
+static inline void tcp_authopt_update_rcv_sne(struct tcp_sock *tp, u32 seq)
+{
+ struct tcp_authopt_info *info;
+
+ if (tcp_authopt_needed) {
+ info = rcu_dereference_protected(tp->authopt_info,
+ lockdep_sock_is_held((struct sock *)tp));
+ if (info)
+ __tcp_authopt_update_rcv_sne(tp, info, seq);
+ }
+}
+void __tcp_authopt_update_snd_sne(struct tcp_sock *tp, struct tcp_authopt_info *info, u32 seq);
+static inline void tcp_authopt_update_snd_sne(struct tcp_sock *tp, u32 seq)
+{
+ struct tcp_authopt_info *info;
+
+ if (tcp_authopt_needed) {
+ info = rcu_dereference_protected(tp->authopt_info,
+ lockdep_sock_is_held((struct sock *)tp));
+ if (info)
+ __tcp_authopt_update_snd_sne(tp, info, seq);
+ }
+}
#else
static inline int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen)
{
return -ENOPROTOOPT;
}
@@ -235,8 +263,14 @@ static inline int tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb
static inline int tcp_authopt_inbound_check_req(struct request_sock *sk, struct sk_buff *skb,
const u8 *opt)
{
return 0;
}
+static inline void tcp_authopt_update_rcv_sne(struct tcp_sock *tp, u32 seq)
+{
+}
+static inline void tcp_authopt_update_snd_sne(struct tcp_sock *tp, u32 seq)
+{
+}
#endif

#endif /* _LINUX_TCP_AUTHOPT_H */
diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index 939dfb4a6f12..5e240b4ada60 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -649,10 +649,97 @@ static int tcp_authopt_get_isn(struct sock *sk,
*disn = htonl(info->dst_isn);
}
return 0;
}

+/* compute_sne - Calculate Sequence Number Extension
+ *
+ * Give old upper/lower 32bit values and a new lower 32bit value determine the
+ * new value of the upper 32 bit. The new sequence number can be 2^31 before or
+ * after prev_seq but TCP window scaling should limit this further.
+ *
+ * For correct accounting the stored SNE value should be only updated together
+ * with the SEQ.
+ */
+static u32 compute_sne(u32 sne, u32 prev_seq, u32 seq)
+{
+ if (before(seq, prev_seq)) {
+ if (seq > prev_seq)
+ --sne;
+ } else {
+ if (seq < prev_seq)
+ ++sne;
+ }
+
+ return sne;
+}
+
+/* Update rcv_sne, must be called immediately before rcv_nxt update */
+void __tcp_authopt_update_rcv_sne(struct tcp_sock *tp,
+ struct tcp_authopt_info *info, u32 seq)
+{
+ info->rcv_sne = compute_sne(info->rcv_sne, tp->rcv_nxt, seq);
+}
+
+/* Update snd_sne, must be called immediately before snd_nxt update */
+void __tcp_authopt_update_snd_sne(struct tcp_sock *tp,
+ struct tcp_authopt_info *info, u32 seq)
+{
+ info->snd_sne = compute_sne(info->snd_sne, tp->snd_nxt, seq);
+}
+
+/* Compute SNE for a specific packet (by seq). */
+static int compute_packet_sne(struct sock *sk, struct tcp_authopt_info *info,
+ u32 seq, bool input, __be32 *sne)
+{
+ u32 rcv_nxt, snd_nxt;
+
+ // For TCP_NEW_SYN_RECV we have no tcp_authopt_info but tcp_request_sock holds ISN.
+ if (sk->sk_state == TCP_NEW_SYN_RECV) {
+ struct tcp_request_sock *rsk = tcp_rsk((struct request_sock *)sk);
+
+ if (input)
+ *sne = htonl(compute_sne(0, rsk->rcv_isn, seq));
+ else
+ *sne = htonl(compute_sne(0, rsk->snt_isn, seq));
+ return 0;
+ }
+
+ /* TCP_LISTEN only receives SYN */
+ if (sk->sk_state == TCP_LISTEN && input)
+ return 0;
+
+ /* TCP_SYN_SENT only sends SYN and receives SYN/ACK
+ * For the input case rcv_nxt is initialized after the packet is
+ * validated so tcp_sk(sk)->rcv_nxt is not initialized.
+ */
+ if (sk->sk_state == TCP_SYN_SENT)
+ return 0;
+
+ if (sk->sk_state == TCP_TIME_WAIT) {
+ rcv_nxt = tcp_twsk(sk)->tw_rcv_nxt;
+ snd_nxt = tcp_twsk(sk)->tw_snd_nxt;
+ } else {
+ if (WARN_ONCE(!sk_fullsock(sk),
+ "unexpected minisock sk=%p state=%d", sk,
+ sk->sk_state))
+ return -EINVAL;
+ rcv_nxt = tcp_sk(sk)->rcv_nxt;
+ snd_nxt = tcp_sk(sk)->snd_nxt;
+ }
+
+ if (WARN_ONCE(!info, "unexpected missing info for sk=%p sk_state=%d", sk, sk->sk_state))
+ return -EINVAL;
+
+ if (input)
+ *sne = htonl(compute_sne(info->rcv_sne, rcv_nxt, seq));
+ else
+ *sne = htonl(compute_sne(info->snd_sne, snd_nxt, seq));
+
+ return 0;
+}
+
/* Feed one buffer into ahash
* The buffer is assumed to be DMA-able
*/
static int crypto_ahash_buf(struct ahash_request *req, u8 *buf, uint len)
{
@@ -684,10 +771,13 @@ int __tcp_authopt_openreq(struct sock *newsk, const struct sock *oldsk, struct r
if (!new_info)
return -ENOMEM;

new_info->src_isn = tcp_rsk(req)->snt_isn;
new_info->dst_isn = tcp_rsk(req)->rcv_isn;
+ /* Caller is tcp_create_openreq_child and already initializes snd_nxt/rcv_nxt */
+ new_info->snd_sne = compute_sne(0, new_info->src_isn, tcp_sk(newsk)->snd_nxt);
+ new_info->rcv_sne = compute_sne(0, new_info->dst_isn, tcp_sk(newsk)->rcv_nxt);
sk_gso_disable(newsk);
rcu_assign_pointer(tcp_sk(newsk)->authopt_info, new_info);

return 0;
}
@@ -695,10 +785,12 @@ int __tcp_authopt_openreq(struct sock *newsk, const struct sock *oldsk, struct r
void __tcp_authopt_finish_connect(struct sock *sk, struct sk_buff *skb,
struct tcp_authopt_info *info)
{
info->src_isn = ntohl(tcp_hdr(skb)->ack_seq) - 1;
info->dst_isn = ntohl(tcp_hdr(skb)->seq);
+ info->snd_sne = compute_sne(0, info->src_isn, tcp_sk(sk)->snd_nxt);
+ info->rcv_sne = compute_sne(0, info->dst_isn, tcp_sk(sk)->rcv_nxt);
}

/* feed traffic key into ahash */
static int tcp_authopt_ahash_traffic_key(struct tcp_authopt_alg_pool *pool,
struct sock *sk,
@@ -952,14 +1044,16 @@ static int tcp_authopt_hash_packet(struct tcp_authopt_alg_pool *pool,
bool ipv6,
bool include_options,
u8 *macbuf)
{
struct tcphdr *th = tcp_hdr(skb);
+ __be32 sne = 0;
int err;

- /* NOTE: SNE unimplemented */
- __be32 sne = 0;
+ err = compute_packet_sne(sk, info, ntohl(th->seq), input, &sne);
+ if (err)
+ return err;

err = crypto_ahash_init(pool->req);
if (err)
return err;

diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 5bdb8c31b943..91f1b04c1933 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -3517,10 +3517,11 @@ static void tcp_snd_una_update(struct tcp_sock *tp, u32 ack)
static void tcp_rcv_nxt_update(struct tcp_sock *tp, u32 seq)
{
u32 delta = seq - tp->rcv_nxt;

sock_owned_by_me((struct sock *)tp);
+ tcp_authopt_update_rcv_sne(tp, seq);
tp->bytes_received += delta;
WRITE_ONCE(tp->rcv_nxt, seq);
}

/* Update our send window.
diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
index b959e8b949b6..6a6024a0b9e9 100644
--- a/net/ipv4/tcp_output.c
+++ b/net/ipv4/tcp_output.c
@@ -67,10 +67,11 @@ static void tcp_event_new_data_sent(struct sock *sk, struct sk_buff *skb)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
unsigned int prior_packets = tp->packets_out;

+ tcp_authopt_update_snd_sne(tp, TCP_SKB_CB(skb)->end_seq);
WRITE_ONCE(tp->snd_nxt, TCP_SKB_CB(skb)->end_seq);

__skb_unlink(skb, &sk->sk_write_queue);
tcp_rbtree_insert(&sk->tcp_rtx_queue, skb);

--
2.25.1

2022-01-24 19:08:49

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 12/20] tcp: authopt: Add key selection controls

The RFC requires that TCP can report the keyid and rnextkeyid values
being sent or received, implement this via getsockopt values.

The RFC also requires that user can select the sending key and that the
sending key is automatically switched based on rnextkeyid. These
requirements can conflict so we implement both and add a flag which
specifies if user or peer request takes priority.

Also add an option to control rnextkeyid explicitly from userspace.

Signed-off-by: Leonard Crestez <[email protected]>
---
Documentation/networking/tcp_authopt.rst | 25 ++++++
include/net/tcp_authopt.h | 40 ++++++++-
include/uapi/linux/tcp.h | 31 +++++++
net/ipv4/tcp_authopt.c | 107 ++++++++++++++++++++++-
net/ipv4/tcp_ipv4.c | 2 +-
net/ipv6/tcp_ipv6.c | 2 +-
6 files changed, 200 insertions(+), 7 deletions(-)

diff --git a/Documentation/networking/tcp_authopt.rst b/Documentation/networking/tcp_authopt.rst
index 72adb7a891ce..f29fdea7769f 100644
--- a/Documentation/networking/tcp_authopt.rst
+++ b/Documentation/networking/tcp_authopt.rst
@@ -42,10 +42,35 @@ new flags.

RFC5925 requires that key ids do not overlap when tcp identifiers (addr/port)
overlap. This is not enforced by linux, configuring ambiguous keys will result
in packet drops and lost connections.

+Key selection
+-------------
+
+On getsockopt(TCP_AUTHOPT) information is provided about keyid/rnextkeyid in
+the last send packet and about the keyid/rnextkeyd in the last valid received
+packet.
+
+By default the sending keyid is selected to match the rnextkeyid value sent by
+the remote side. If that keyid is not available (or for new connections) a
+random matching key is selected.
+
+If the ``TCP_AUTHOPT_LOCK_KEYID`` flag is set then the sending key is selected
+by the `tcp_authopt.send_local_id` field and recv_rnextkeyid is ignored. If no
+key with local_id == send_local_id is configured then a random matching key is
+selected.
+
+The current sending key is cached in the socket and will not change unless
+requested by remote rnextkeyid or by setsockopt.
+
+The rnextkeyid value sent on the wire is usually the recv_id of the current
+key used for sending. If the TCP_AUTHOPT_LOCK_RNEXTKEY flag is set in
+`tcp_authopt.flags` the value of `tcp_authopt.send_rnextkeyid` is send
+instead. This can be used to implement smooth rollover: the peer will switch
+its keyid to the received rnextkeyid when it is available.
+
ABI Reference
=============

.. kernel-doc:: include/uapi/linux/tcp.h
:identifiers: tcp_authopt tcp_authopt_flag tcp_authopt_key tcp_authopt_key_flag tcp_authopt_alg
diff --git a/include/net/tcp_authopt.h b/include/net/tcp_authopt.h
index 9ee5165388b1..3d03fbb186ef 100644
--- a/include/net/tcp_authopt.h
+++ b/include/net/tcp_authopt.h
@@ -70,10 +70,45 @@ struct tcp_authopt_info {
u32 dst_isn;
/** @rcv_sne: Recv-side Sequence Number Extension tracking tcp_sock.rcv_nxt */
u32 rcv_sne;
/** @snd_sne: Send-side Sequence Number Extension tracking tcp_sock.snd_nxt */
u32 snd_sne;
+
+ /**
+ * @send_keyid: keyid currently being sent
+ *
+ * This is controlled by userspace by userspace if
+ * TCP_AUTHOPT_FLAG_LOCK_KEYID, otherwise we try to match recv_rnextkeyid
+ */
+ u8 send_keyid;
+ /**
+ * @send_rnextkeyid: rnextkeyid currently being sent
+ *
+ * This is controlled by userspace if TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID is set
+ */
+ u8 send_rnextkeyid;
+ /**
+ * @recv_keyid: last keyid received from remote
+ *
+ * This is reported to userspace but has no other special behavior attached.
+ */
+ u8 recv_keyid;
+ /**
+ * @recv_rnextkeyid: last rnextkeyid received from remote
+ *
+ * Linux tries to honor this unless TCP_AUTHOPT_FLAG_LOCK_KEYID is set
+ */
+ u8 recv_rnextkeyid;
+
+ /**
+ * @send_key: Current key used for sending, cached.
+ *
+ * Once a key is found it only changes by user or remote request.
+ *
+ * Field is protected by the socket lock and holds a kref to the key.
+ */
+ struct tcp_authopt_key_info __rcu *send_key;
};

/* TCP authopt as found in header */
struct tcphdr_authopt {
u8 num;
@@ -95,22 +130,23 @@ int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *key);
int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen);
struct tcp_authopt_key_info *__tcp_authopt_select_key(
const struct sock *sk,
struct tcp_authopt_info *info,
const struct sock *addr_sk,
- u8 *rnextkeyid);
+ u8 *rnextkeyid,
+ bool locked);
static inline struct tcp_authopt_key_info *tcp_authopt_select_key(
const struct sock *sk,
const struct sock *addr_sk,
struct tcp_authopt_info **info,
u8 *rnextkeyid)
{
if (tcp_authopt_needed) {
*info = rcu_dereference(tcp_sk(sk)->authopt_info);

if (*info)
- return __tcp_authopt_select_key(sk, *info, addr_sk, rnextkeyid);
+ return __tcp_authopt_select_key(sk, *info, addr_sk, rnextkeyid, true);
}
return NULL;
}
int tcp_authopt_hash(
char *hash_location,
diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h
index 76d7be6b27f4..e02176390519 100644
--- a/include/uapi/linux/tcp.h
+++ b/include/uapi/linux/tcp.h
@@ -346,10 +346,24 @@ struct tcp_diag_md5sig {

/**
* enum tcp_authopt_flag - flags for `tcp_authopt.flags`
*/
enum tcp_authopt_flag {
+ /**
+ * @TCP_AUTHOPT_FLAG_LOCK_KEYID: keyid controlled by sockopt
+ *
+ * If this is set `tcp_authopt.send_keyid` is used to determined sending
+ * key. Otherwise a key with send_id == recv_rnextkeyid is preferred.
+ */
+ TCP_AUTHOPT_FLAG_LOCK_KEYID = (1 << 0),
+ /**
+ * @TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID: Override rnextkeyid from userspace
+ *
+ * If this is set then `tcp_authopt.send_rnextkeyid` is sent on outbound
+ * packets. Other the recv_id of the current sending key is sent.
+ */
+ TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID = (1 << 1),
/**
* @TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED:
* Configure behavior of segments with TCP-AO coming from hosts for which no
* key is configured. The default recommended by RFC is to silently accept
* such connections.
@@ -361,10 +375,27 @@ enum tcp_authopt_flag {
* struct tcp_authopt - Per-socket options related to TCP Authentication Option
*/
struct tcp_authopt {
/** @flags: Combination of &enum tcp_authopt_flag */
__u32 flags;
+ /**
+ * @send_keyid: `tcp_authopt_key.send_id` of preferred send key
+ *
+ * This is only used if `TCP_AUTHOPT_FLAG_LOCK_KEYID` is set.
+ */
+ __u8 send_keyid;
+ /**
+ * @send_rnextkeyid: The rnextkeyid to send in packets
+ *
+ * This is controlled by the user iff TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID is
+ * set. Otherwise rnextkeyid is the recv_id of the current key.
+ */
+ __u8 send_rnextkeyid;
+ /** @recv_keyid: A recently-received keyid value. Only for getsockopt. */
+ __u8 recv_keyid;
+ /** @recv_rnextkeyid: A recently-received rnextkeyid value. Only for getsockopt. */
+ __u8 recv_rnextkeyid;
};

/**
* enum tcp_authopt_key_flag - flags for `tcp_authopt.flags`
*
diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index d924a73a17c1..c00b23f1e5af 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -361,22 +361,89 @@ static struct tcp_authopt_key_info *tcp_authopt_lookup_send(struct netns_tcp_aut
*
* @sk: socket
* @info: socket's tcp_authopt_info
* @addr_sk: socket used for address lookup. Same as sk except for synack case
* @rnextkeyid: value of rnextkeyid caller should write in packet
+ * @locked: If we're holding the socket lock. This is false for some timewait and reset cases
*
* Result is protected by RCU and can't be stored, it may only be passed to
* tcp_authopt_hash and only under a single rcu_read_lock.
*/
struct tcp_authopt_key_info *__tcp_authopt_select_key(const struct sock *sk,
struct tcp_authopt_info *info,
const struct sock *addr_sk,
- u8 *rnextkeyid)
+ u8 *rnextkeyid,
+ bool locked)
{
+ struct tcp_authopt_key_info *key, *new_key = NULL;
struct netns_tcp_authopt *net = sock_net_tcp_authopt(sk);

- return tcp_authopt_lookup_send(net, addr_sk, -1);
+ /* Listen sockets don't refer to any specific connection so we don't try
+ * to keep using the same key and ignore any received keyids.
+ */
+ if (sk->sk_state == TCP_LISTEN) {
+ int send_keyid = -1;
+
+ if (info->flags & TCP_AUTHOPT_FLAG_LOCK_KEYID)
+ send_keyid = info->send_keyid;
+ key = tcp_authopt_lookup_send(net, addr_sk, send_keyid);
+ if (key)
+ *rnextkeyid = key->recv_id;
+
+ return key;
+ }
+
+ if (locked) {
+ sock_owned_by_me(sk);
+ key = rcu_dereference_protected(info->send_key, lockdep_sock_is_held(sk));
+ if (key && (key->flags & TCP_AUTHOPT_KEY_DEL) == TCP_AUTHOPT_KEY_DEL) {
+ info->send_key = NULL;
+ tcp_authopt_key_put(key);
+ key = NULL;
+ }
+ } else {
+ key = NULL;
+ }
+
+ /* Try to keep the same sending key unless user or peer requires a different key
+ * User request (via TCP_AUTHOPT_FLAG_LOCK_KEYID) always overrides peer request.
+ */
+ if (info->flags & TCP_AUTHOPT_FLAG_LOCK_KEYID) {
+ int send_keyid = info->send_keyid;
+
+ if (!key || key->send_id != send_keyid)
+ new_key = tcp_authopt_lookup_send(net, addr_sk, send_keyid);
+ } else {
+ if (!key || key->send_id != info->recv_rnextkeyid)
+ new_key = tcp_authopt_lookup_send(net, addr_sk, info->recv_rnextkeyid);
+ }
+ /* If no key found with specific send_id try anything else. */
+ if (!key && !new_key)
+ new_key = tcp_authopt_lookup_send(net, addr_sk, -1);
+
+ /* Update current key only if we hold the socket lock. */
+ if (new_key && key != new_key) {
+ if (locked) {
+ if (kref_get_unless_zero(&new_key->ref)) {
+ rcu_assign_pointer(info->send_key, new_key);
+ tcp_authopt_key_put(key);
+ }
+ /* If key was deleted it's still valid until the end of
+ * the RCU grace period.
+ */
+ }
+ key = new_key;
+ }
+
+ if (key) {
+ if (info->flags & TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID)
+ *rnextkeyid = info->send_rnextkeyid;
+ else
+ *rnextkeyid = info->send_rnextkeyid = key->recv_id;
+ }
+
+ return key;
}
EXPORT_SYMBOL(__tcp_authopt_select_key);

static struct tcp_authopt_info *__tcp_authopt_info_get_or_create(struct sock *sk)
{
@@ -398,10 +465,12 @@ static struct tcp_authopt_info *__tcp_authopt_info_get_or_create(struct sock *sk

return info;
}

#define TCP_AUTHOPT_KNOWN_FLAGS ( \
+ TCP_AUTHOPT_FLAG_LOCK_KEYID | \
+ TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID | \
TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED)

/* Like copy_from_sockopt except tolerate different optlen for compatibility reasons
*
* If the src is shorter then it's from an old userspace and the rest of dst is
@@ -469,18 +538,23 @@ int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen)
info = __tcp_authopt_info_get_or_create(sk);
if (IS_ERR(info))
return PTR_ERR(info);

info->flags = opt.flags & TCP_AUTHOPT_KNOWN_FLAGS;
+ if (opt.flags & TCP_AUTHOPT_FLAG_LOCK_KEYID)
+ info->send_keyid = opt.send_keyid;
+ if (opt.flags & TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID)
+ info->send_rnextkeyid = opt.send_rnextkeyid;

return 0;
}

int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt)
{
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_authopt_info *info;
+ struct tcp_authopt_key_info *send_key;
int err;

memset(opt, 0, sizeof(*opt));
sock_owned_by_me(sk);
err = check_sysctl_tcp_authopt();
@@ -490,10 +564,22 @@ int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt)
info = rcu_dereference_check(tp->authopt_info, lockdep_sock_is_held(sk));
if (!info)
return -ENOENT;

opt->flags = info->flags & TCP_AUTHOPT_KNOWN_FLAGS;
+ /* These keyids might be undefined, for example before connect.
+ * Reporting zero is not strictly correct because there are no reserved
+ * values.
+ */
+ send_key = rcu_dereference_check(info->send_key, lockdep_sock_is_held(sk));
+ if (send_key)
+ opt->send_keyid = send_key->send_id;
+ else
+ opt->send_keyid = 0;
+ opt->send_rnextkeyid = info->send_rnextkeyid;
+ opt->recv_keyid = info->recv_keyid;
+ opt->recv_rnextkeyid = info->recv_rnextkeyid;

return 0;
}

#define TCP_AUTHOPT_KEY_KNOWN_FLAGS ( \
@@ -1442,11 +1528,11 @@ int __tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb,
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTHOPTFAILURE);
print_tcpao_notice("TCP Authentication Unexpected: Rejected", skb);
return -EINVAL;
}
print_tcpao_notice("TCP Authentication Unexpected: Accepted", skb);
- return 0;
+ goto accept;
}
if (opt && !key) {
/* Keys are configured for peer but with different keyid than packet */
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTHOPTFAILURE);
print_tcpao_notice("TCP Authentication Failed", skb);
@@ -1465,10 +1551,25 @@ int __tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb,
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTHOPTFAILURE);
print_tcpao_notice("TCP Authentication Failed", skb);
return -EINVAL;
}

+accept:
+ /* Doing this for all valid packets will results in keyids temporarily
+ * flipping back and forth if packets are reordered or retransmitted
+ * but keys should eventually stabilize.
+ *
+ * This is connection-specific so don't store for listen sockets.
+ *
+ * We could store rnextkeyid from SYN in a request sock and use it for
+ * the SYNACK but we don't.
+ */
+ if (sk->sk_state != TCP_LISTEN) {
+ info->recv_keyid = opt->keyid;
+ info->recv_rnextkeyid = opt->rnextkeyid;
+ }
+
return 1;
}
EXPORT_SYMBOL(__tcp_authopt_inbound_check);

static int tcp_authopt_init_net(struct net *full_net)
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index e35463a378e7..f05f884c3b04 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -664,11 +664,11 @@ static int tcp_v4_authopt_handle_reply(const struct sock *sk,
info = tcp_twsk(sk)->tw_authopt_info;
else
info = rcu_dereference_check(tcp_sk(sk)->authopt_info, lockdep_sock_is_held(sk));
if (!info)
return 0;
- key_info = __tcp_authopt_select_key(sk, info, sk, &rnextkeyid);
+ key_info = __tcp_authopt_select_key(sk, info, sk, &rnextkeyid, false);
if (!key_info)
return 0;
*optptr = htonl((TCPOPT_AUTHOPT << 24) |
(TCPOLEN_AUTHOPT_OUTPUT << 16) |
(key_info->send_id << 8) |
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index b72d57565c18..8fcf71fc1081 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -901,11 +901,11 @@ static int tcp_v6_send_response_init_authopt(const struct sock *sk,
*info = tcp_twsk(sk)->tw_authopt_info;
else
*info = rcu_dereference(tcp_sk(sk)->authopt_info);
if (!*info)
return 0;
- *key = __tcp_authopt_select_key(sk, *info, sk, rnextkeyid);
+ *key = __tcp_authopt_select_key(sk, *info, sk, rnextkeyid, false);
if (*key)
return TCPOLEN_AUTHOPT_OUTPUT;
return 0;
}

--
2.25.1

2022-01-24 19:08:50

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 11/20] tcp: ipv4: Add AO signing for skb-less replies

The code in tcp_v4_send_ack and tcp_v4_send_reset does not allocate a
full skb so special handling is required for tcp-authopt handling.

Signed-off-by: Leonard Crestez <[email protected]>
---
net/ipv4/tcp_authopt.c | 3 +-
net/ipv4/tcp_ipv4.c | 84 ++++++++++++++++++++++++++++++++++++++++--
2 files changed, 83 insertions(+), 4 deletions(-)

diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index 7a2e15dd80ba..d924a73a17c1 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -955,10 +955,11 @@ static int tcp_v4_authopt_get_traffic_key_noskb(struct tcp_authopt_key_info *key
u8 *traffic_key)
{
int err;
struct tcp_authopt_alg_pool *pool;
struct tcp_v4_authopt_context_data data;
+ char traffic_key_context_header[7] = "\x01TCP-AO";

BUILD_BUG_ON(sizeof(data) != 22);

pool = tcp_authopt_get_kdf_pool(key);
if (IS_ERR(pool))
@@ -971,11 +972,11 @@ static int tcp_v4_authopt_get_traffic_key_noskb(struct tcp_authopt_key_info *key
if (err)
goto out;

// RFC5926 section 3.1.1.1
// Separate to keep alignment semi-sane
- err = crypto_ahash_buf(pool->req, "\x01TCP-AO", 7);
+ err = crypto_ahash_buf(pool->req, traffic_key_context_header, 7);
if (err)
return err;
data.saddr = saddr;
data.daddr = daddr;
data.sport = sport;
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 5a7fe973bc4e..e35463a378e7 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -644,10 +644,50 @@ void tcp_v4_send_check(struct sock *sk, struct sk_buff *skb)

__tcp_v4_send_check(skb, inet->inet_saddr, inet->inet_daddr);
}
EXPORT_SYMBOL(tcp_v4_send_check);

+#ifdef CONFIG_TCP_AUTHOPT
+/** tcp_v4_authopt_handle_reply - Insert TCPOPT_AUTHOPT if required
+ *
+ * returns number of bytes (always aligned to 4) or zero
+ */
+static int tcp_v4_authopt_handle_reply(const struct sock *sk,
+ struct sk_buff *skb,
+ __be32 *optptr,
+ struct tcphdr *th)
+{
+ struct tcp_authopt_info *info;
+ struct tcp_authopt_key_info *key_info;
+ u8 rnextkeyid;
+
+ if (sk->sk_state == TCP_TIME_WAIT)
+ info = tcp_twsk(sk)->tw_authopt_info;
+ else
+ info = rcu_dereference_check(tcp_sk(sk)->authopt_info, lockdep_sock_is_held(sk));
+ if (!info)
+ return 0;
+ key_info = __tcp_authopt_select_key(sk, info, sk, &rnextkeyid);
+ if (!key_info)
+ return 0;
+ *optptr = htonl((TCPOPT_AUTHOPT << 24) |
+ (TCPOLEN_AUTHOPT_OUTPUT << 16) |
+ (key_info->send_id << 8) |
+ (rnextkeyid));
+ /* must update doff before signature computation */
+ th->doff += TCPOLEN_AUTHOPT_OUTPUT / 4;
+ tcp_v4_authopt_hash_reply((char *)(optptr + 1),
+ info,
+ key_info,
+ ip_hdr(skb)->daddr,
+ ip_hdr(skb)->saddr,
+ th);
+
+ return TCPOLEN_AUTHOPT_OUTPUT;
+}
+#endif
+
/*
* This routine will send an RST to the other tcp.
*
* Someone asks: why I NEVER use socket parameters (TOS, TTL etc.)
* for reset.
@@ -659,10 +699,12 @@ EXPORT_SYMBOL(tcp_v4_send_check);
* Exception: precedence violation. We do not implement it in any case.
*/

#ifdef CONFIG_TCP_MD5SIG
#define OPTION_BYTES TCPOLEN_MD5SIG_ALIGNED
+#elif defined(OPTION_BYTES_TCP_AUTHOPT)
+#define OPTION_BYTES TCPOLEN_AUTHOPT_OUTPUT
#else
#define OPTION_BYTES sizeof(__be32)
#endif

static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb)
@@ -712,12 +754,29 @@ static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb)
memset(&arg, 0, sizeof(arg));
arg.iov[0].iov_base = (unsigned char *)&rep;
arg.iov[0].iov_len = sizeof(rep.th);

net = sk ? sock_net(sk) : dev_net(skb_dst(skb)->dev);
-#ifdef CONFIG_TCP_MD5SIG
+#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AUTHOPT)
rcu_read_lock();
+#endif
+#ifdef CONFIG_TCP_AUTHOPT
+ /* Unlike TCP-MD5 the signatures for TCP-AO depend on initial sequence
+ * numbers so we can only handle established and time-wait sockets.
+ */
+ if (tcp_authopt_needed && sk &&
+ sk->sk_state != TCP_NEW_SYN_RECV &&
+ sk->sk_state != TCP_LISTEN) {
+ int tcp_authopt_ret = tcp_v4_authopt_handle_reply(sk, skb, rep.opt, &rep.th);
+
+ if (tcp_authopt_ret) {
+ arg.iov[0].iov_len += tcp_authopt_ret;
+ goto skip_md5sig;
+ }
+ }
+#endif
+#ifdef CONFIG_TCP_MD5SIG
hash_location = tcp_parse_md5sig_option(th);
if (sk && sk_fullsock(sk)) {
const union tcp_md5_addr *addr;
int l3index;

@@ -755,11 +814,10 @@ static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb)
addr = (union tcp_md5_addr *)&ip_hdr(skb)->saddr;
key = tcp_md5_do_lookup(sk1, l3index, addr, AF_INET);
if (!key)
goto out;

-
genhash = tcp_v4_md5_hash_skb(newhash, key, NULL, skb);
if (genhash || memcmp(hash_location, newhash, 16) != 0)
goto out;

}
@@ -775,10 +833,13 @@ static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb)

tcp_v4_md5_hash_hdr((__u8 *) &rep.opt[1],
key, ip_hdr(skb)->saddr,
ip_hdr(skb)->daddr, &rep.th);
}
+#endif
+#ifdef CONFIG_TCP_AUTHOPT
+skip_md5sig:
#endif
/* Can't co-exist with TCPMD5, hence check rep.opt[0] */
if (rep.opt[0] == 0) {
__be32 mrst = mptcp_reset_option(skb);

@@ -828,12 +889,14 @@ static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb)
ctl_sk->sk_mark = 0;
__TCP_INC_STATS(net, TCP_MIB_OUTSEGS);
__TCP_INC_STATS(net, TCP_MIB_OUTRSTS);
local_bh_enable();

-#ifdef CONFIG_TCP_MD5SIG
+#if defined(CONFIG_TCP_MD5SIG)
out:
+#endif
+#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AUTHOPT)
rcu_read_unlock();
#endif
}

/* The code following below sending ACKs in SYN-RECV and TIME-WAIT states
@@ -850,10 +913,12 @@ static void tcp_v4_send_ack(const struct sock *sk,
struct {
struct tcphdr th;
__be32 opt[(TCPOLEN_TSTAMP_ALIGNED >> 2)
#ifdef CONFIG_TCP_MD5SIG
+ (TCPOLEN_MD5SIG_ALIGNED >> 2)
+#elif defined(CONFIG_TCP_AUTHOPT)
+ + (TCPOLEN_AUTHOPT_OUTPUT >> 2)
#endif
];
} rep;
struct net *net = sock_net(sk);
struct ip_reply_arg arg;
@@ -881,10 +946,23 @@ static void tcp_v4_send_ack(const struct sock *sk,
rep.th.seq = htonl(seq);
rep.th.ack_seq = htonl(ack);
rep.th.ack = 1;
rep.th.window = htons(win);

+#ifdef CONFIG_TCP_AUTHOPT
+ if (tcp_authopt_needed) {
+ int aoret, offset = (tsecr) ? 3 : 0;
+
+ aoret = tcp_v4_authopt_handle_reply(sk, skb, &rep.opt[offset], &rep.th);
+ if (aoret) {
+ arg.iov[0].iov_len += aoret;
+#ifdef CONFIG_TCP_MD5SIG
+ key = NULL;
+#endif
+ }
+ }
+#endif
#ifdef CONFIG_TCP_MD5SIG
if (key) {
int offset = (tsecr) ? 3 : 0;

rep.opt[offset++] = htonl((TCPOPT_NOP << 24) |
--
2.25.1

2022-01-24 19:08:53

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 15/20] tcp: authopt: Add prefixlen support

This allows making a key apply to an addr/prefix instead of just the
full addr. This is enabled through a custom flag, default behavior is
still full address match.

This is equivalent to TCP_MD5SIG_FLAG_PREFIX from TCP_MD5SIG and has
the same use-cases.

Signed-off-by: Leonard Crestez <[email protected]>
---
Documentation/networking/tcp_authopt.rst | 1 +
include/net/tcp_authopt.h | 2 +
include/uapi/linux/tcp.h | 10 ++++
net/ipv4/tcp_authopt.c | 63 ++++++++++++++++++++++--
4 files changed, 72 insertions(+), 4 deletions(-)

diff --git a/Documentation/networking/tcp_authopt.rst b/Documentation/networking/tcp_authopt.rst
index f681d2221ce3..6520c6d02755 100644
--- a/Documentation/networking/tcp_authopt.rst
+++ b/Documentation/networking/tcp_authopt.rst
@@ -38,10 +38,11 @@ new flags.

* Address binding is optional, by default keys match all addresses
* Local address is ignored, matching is done by remote address
* Ports are ignored
* It is possible to match a specific VRF by l3index (default is to ignore)
+ * It is possible to match with a fixed prefixlen (default is full address)

RFC5925 requires that key ids do not overlap when tcp identifiers (addr/port)
overlap. This is not enforced by linux, configuring ambiguous keys will result
in packet drops and lost connections.

diff --git a/include/net/tcp_authopt.h b/include/net/tcp_authopt.h
index 800fde277239..743248904552 100644
--- a/include/net/tcp_authopt.h
+++ b/include/net/tcp_authopt.h
@@ -47,10 +47,12 @@ struct tcp_authopt_key_info {
u8 keylen;
/** @key: Same as &tcp_authopt_key.key */
u8 key[TCP_AUTHOPT_MAXKEYLEN];
/** @l3index: Same as &tcp_authopt_key.ifindex */
int l3index;
+ /** @prefix: Length of addr match (default full) */
+ int prefixlen;
/** @addr: Same as &tcp_authopt_key.addr */
struct sockaddr_storage addr;
/** @alg: Algorithm implementation matching alg_id */
struct tcp_authopt_alg_imp *alg;
};
diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h
index ed27feb93b0e..b1063e1e1b9f 100644
--- a/include/uapi/linux/tcp.h
+++ b/include/uapi/linux/tcp.h
@@ -403,18 +403,21 @@ struct tcp_authopt {
* @TCP_AUTHOPT_KEY_EXCLUDE_OPTS: Exclude TCP options from signature
* @TCP_AUTHOPT_KEY_ADDR_BIND: Key only valid for `tcp_authopt.addr`
* @TCP_AUTHOPT_KEY_IFINDEX: Key only valid for `tcp_authopt.ifindex`
* @TCP_AUTHOPT_KEY_NOSEND: Key invalid for send (expired)
* @TCP_AUTHOPT_KEY_NORECV: Key invalid for recv (expired)
+ * @TCP_AUTHOPT_KEY_PREFIXLEN: Valid value in `tcp_authopt.prefixlen`, otherwise
+ * match full address length
*/
enum tcp_authopt_key_flag {
TCP_AUTHOPT_KEY_DEL = (1 << 0),
TCP_AUTHOPT_KEY_EXCLUDE_OPTS = (1 << 1),
TCP_AUTHOPT_KEY_ADDR_BIND = (1 << 2),
TCP_AUTHOPT_KEY_IFINDEX = (1 << 3),
TCP_AUTHOPT_KEY_NOSEND = (1 << 4),
TCP_AUTHOPT_KEY_NORECV = (1 << 5),
+ TCP_AUTHOPT_KEY_PREFIXLEN = (1 << 6),
};

/**
* enum tcp_authopt_alg - Algorithms for TCP Authentication Option
*/
@@ -465,10 +468,17 @@ struct tcp_authopt_key {
* connections through this interface. Interface must be an vrf master.
*
* This is similar to `tcp_msg5sig.tcpm_ifindex`
*/
int ifindex;
+ /**
+ * @prefixlen: length of prefix to match
+ *
+ * Without the TCP_AUTHOPT_KEY_PREFIXLEN flag this is ignored and a full
+ * address match is performed.
+ */
+ int prefixlen;
};

/* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */

#define TCP_RECEIVE_ZEROCOPY_FLAG_TLB_CLEAN_HINT 0x1
diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index 331bf3e8b66a..c4b3c3e0e9ca 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -4,10 +4,11 @@
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/tcp.h>
#include <linux/kref.h>
#include <crypto/hash.h>
+#include <linux/inetdevice.h>

/* This is mainly intended to protect against local privilege escalations through
* a rarely used feature so it is deliberately not namespaced.
*/
int sysctl_tcp_authopt;
@@ -269,10 +270,14 @@ static bool tcp_authopt_key_match_exact(struct tcp_authopt_key_info *info,
return false;
if ((info->flags & TCP_AUTHOPT_KEY_IFINDEX) != (key->flags & TCP_AUTHOPT_KEY_IFINDEX))
return false;
if ((info->flags & TCP_AUTHOPT_KEY_IFINDEX) && info->l3index != key->ifindex)
return false;
+ if ((info->flags & TCP_AUTHOPT_KEY_PREFIXLEN) != (key->flags & TCP_AUTHOPT_KEY_PREFIXLEN))
+ return false;
+ if ((info->flags & TCP_AUTHOPT_KEY_PREFIXLEN) && info->prefixlen != key->prefixlen)
+ return false;
if ((info->flags & TCP_AUTHOPT_KEY_ADDR_BIND) != (key->flags & TCP_AUTHOPT_KEY_ADDR_BIND))
return false;
if (info->flags & TCP_AUTHOPT_KEY_ADDR_BIND)
if (!ipvx_addr_match(&info->addr, &key->addr))
return false;
@@ -286,17 +291,20 @@ static bool tcp_authopt_key_match_skb_addr(struct tcp_authopt_key_info *key,
u16 keyaf = key->addr.ss_family;
struct iphdr *iph = (struct iphdr *)skb_network_header(skb);

if (keyaf == AF_INET && iph->version == 4) {
struct sockaddr_in *key_addr = (struct sockaddr_in *)&key->addr;
+ __be32 mask = inet_make_mask(key->prefixlen);

- return iph->saddr == key_addr->sin_addr.s_addr;
+ return (iph->saddr & mask) == key_addr->sin_addr.s_addr;
} else if (keyaf == AF_INET6 && iph->version == 6) {
struct ipv6hdr *ip6h = (struct ipv6hdr *)skb_network_header(skb);
struct sockaddr_in6 *key_addr = (struct sockaddr_in6 *)&key->addr;

- return ipv6_addr_equal(&ip6h->saddr, &key_addr->sin6_addr);
+ return ipv6_prefix_equal(&ip6h->saddr,
+ &key_addr->sin6_addr,
+ key->prefixlen);
}

/* This actually happens with ipv6-mapped-ipv4-addresses
* IPv6 listen sockets will be asked to validate ipv4 packets.
*/
@@ -312,17 +320,20 @@ static bool tcp_authopt_key_match_sk_addr(struct tcp_authopt_key_info *key,
if (keyaf != addr_sk->sk_family)
return false;

if (keyaf == AF_INET) {
struct sockaddr_in *key_addr = (struct sockaddr_in *)&key->addr;
+ __be32 mask = inet_make_mask(key->prefixlen);

- return addr_sk->sk_daddr == key_addr->sin_addr.s_addr;
+ return (addr_sk->sk_daddr & mask) == key_addr->sin_addr.s_addr;
#if IS_ENABLED(CONFIG_IPV6)
} else if (keyaf == AF_INET6) {
struct sockaddr_in6 *key_addr = (struct sockaddr_in6 *)&key->addr;

- return ipv6_addr_equal(&addr_sk->sk_v6_daddr, &key_addr->sin6_addr);
+ return ipv6_prefix_equal(&addr_sk->sk_v6_daddr,
+ &key_addr->sin6_addr,
+ key->prefixlen);
#endif
}

return false;
}
@@ -348,10 +359,16 @@ static bool better_key_match(struct tcp_authopt_key_info *old, struct tcp_authop
/* l3index always overrides non-l3index */
if (old->l3index && new->l3index == 0)
return false;
if (old->l3index == 0 && new->l3index)
return true;
+ /* Full address match overrides match by prefixlen */
+ if (!(new->flags & TCP_AUTHOPT_KEY_PREFIXLEN) && (old->flags & TCP_AUTHOPT_KEY_PREFIXLEN))
+ return false;
+ /* Longer prefixes are better matches */
+ if (new->prefixlen > old->prefixlen)
+ return true;

return false;
}

static struct tcp_authopt_key_info *tcp_authopt_lookup_send(struct netns_tcp_authopt *net,
@@ -615,21 +632,32 @@ int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt)
#define TCP_AUTHOPT_KEY_KNOWN_FLAGS ( \
TCP_AUTHOPT_KEY_DEL | \
TCP_AUTHOPT_KEY_EXCLUDE_OPTS | \
TCP_AUTHOPT_KEY_ADDR_BIND | \
TCP_AUTHOPT_KEY_IFINDEX | \
+ TCP_AUTHOPT_KEY_PREFIXLEN | \
TCP_AUTHOPT_KEY_NOSEND | \
TCP_AUTHOPT_KEY_NORECV)

+static bool ipv6_addr_is_prefix(struct in6_addr *addr, int plen)
+{
+ struct in6_addr copy;
+
+ ipv6_addr_prefix_copy(&copy, addr, plen);
+
+ return !!memcmp(&copy, addr, sizeof(*addr));
+}
+
int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
{
struct tcp_authopt_key opt;
struct tcp_authopt_info *info;
struct tcp_authopt_key_info *key_info, *old_key_info;
struct netns_tcp_authopt *net = sock_net_tcp_authopt(sk);
struct tcp_authopt_alg_imp *alg;
int l3index = 0;
+ int prefixlen;
int err;

sock_owned_by_me(sk);
err = check_sysctl_tcp_authopt();
if (err)
@@ -665,10 +693,36 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
if (opt.flags & TCP_AUTHOPT_KEY_ADDR_BIND) {
if (sk->sk_family != opt.addr.ss_family)
return -EINVAL;
}

+ /* check prefixlen */
+ if (opt.flags & TCP_AUTHOPT_KEY_PREFIXLEN) {
+ prefixlen = opt.prefixlen;
+ if (sk->sk_family == AF_INET) {
+ if (prefixlen < 0 || prefixlen > 32)
+ return -EINVAL;
+ if (((struct sockaddr_in *)&opt.addr)->sin_addr.s_addr &
+ ~inet_make_mask(prefixlen))
+ return -EINVAL;
+ }
+ if (sk->sk_family == AF_INET6) {
+ if (prefixlen < 0 || prefixlen > 128)
+ return -EINVAL;
+ if (!ipv6_addr_is_prefix(&((struct sockaddr_in6 *)&opt.addr)->sin6_addr,
+ prefixlen))
+ return -EINVAL;
+ }
+ } else {
+ if (sk->sk_family == AF_INET)
+ prefixlen = 32;
+ else if (sk->sk_family == AF_INET6)
+ prefixlen = 128;
+ else
+ return -EINVAL;
+ }
+
/* Initialize tcp_authopt_info if not already set */
info = __tcp_authopt_info_get_or_create(sk);
if (IS_ERR(info))
return PTR_ERR(info);

@@ -714,10 +768,11 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
key_info->alg = alg;
key_info->keylen = opt.keylen;
memcpy(key_info->key, opt.key, opt.keylen);
memcpy(&key_info->addr, &opt.addr, sizeof(key_info->addr));
key_info->l3index = l3index;
+ key_info->prefixlen = prefixlen;
hlist_add_head_rcu(&key_info->node, &net->head);
mutex_unlock(&net->mutex);

return 0;
}
--
2.25.1

2022-01-24 19:08:53

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 13/20] tcp: authopt: Add initial l3index support

This is a parallel feature to tcp_md5sig.tcpm_ifindex support and allows
applications to server multiple VRFs with a single socket.

The ifindex argument must be the ifindex of a VRF device and must match
exactly, keys with ifindex == 0 (outside of VRF) will not match for
connections inside a VRF.

Keys without the TCP_AUTHOPT_KEY_IFINDEX will ignore ifindex and match
both inside and outside VRF.

Signed-off-by: Leonard Crestez <[email protected]>
---
Documentation/networking/tcp_authopt.rst | 1 +
include/net/tcp_authopt.h | 2 +
include/uapi/linux/tcp.h | 11 ++++
net/ipv4/tcp_authopt.c | 71 ++++++++++++++++++++++--
4 files changed, 80 insertions(+), 5 deletions(-)

diff --git a/Documentation/networking/tcp_authopt.rst b/Documentation/networking/tcp_authopt.rst
index f29fdea7769f..f681d2221ce3 100644
--- a/Documentation/networking/tcp_authopt.rst
+++ b/Documentation/networking/tcp_authopt.rst
@@ -37,10 +37,11 @@ expand over time by increasing the size of `struct tcp_authopt_key` and adding
new flags.

* Address binding is optional, by default keys match all addresses
* Local address is ignored, matching is done by remote address
* Ports are ignored
+ * It is possible to match a specific VRF by l3index (default is to ignore)

RFC5925 requires that key ids do not overlap when tcp identifiers (addr/port)
overlap. This is not enforced by linux, configuring ambiguous keys will result
in packet drops and lost connections.

diff --git a/include/net/tcp_authopt.h b/include/net/tcp_authopt.h
index 3d03fbb186ef..800fde277239 100644
--- a/include/net/tcp_authopt.h
+++ b/include/net/tcp_authopt.h
@@ -45,10 +45,12 @@ struct tcp_authopt_key_info {
u8 alg_id;
/** @keylen: Same as &tcp_authopt_key.keylen */
u8 keylen;
/** @key: Same as &tcp_authopt_key.key */
u8 key[TCP_AUTHOPT_MAXKEYLEN];
+ /** @l3index: Same as &tcp_authopt_key.ifindex */
+ int l3index;
/** @addr: Same as &tcp_authopt_key.addr */
struct sockaddr_storage addr;
/** @alg: Algorithm implementation matching alg_id */
struct tcp_authopt_alg_imp *alg;
};
diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h
index e02176390519..a7f5f918ed5a 100644
--- a/include/uapi/linux/tcp.h
+++ b/include/uapi/linux/tcp.h
@@ -400,15 +400,17 @@ struct tcp_authopt {
* enum tcp_authopt_key_flag - flags for `tcp_authopt.flags`
*
* @TCP_AUTHOPT_KEY_DEL: Delete the key and ignore non-id fields
* @TCP_AUTHOPT_KEY_EXCLUDE_OPTS: Exclude TCP options from signature
* @TCP_AUTHOPT_KEY_ADDR_BIND: Key only valid for `tcp_authopt.addr`
+ * @TCP_AUTHOPT_KEY_IFINDEX: Key only valid for `tcp_authopt.ifindex`
*/
enum tcp_authopt_key_flag {
TCP_AUTHOPT_KEY_DEL = (1 << 0),
TCP_AUTHOPT_KEY_EXCLUDE_OPTS = (1 << 1),
TCP_AUTHOPT_KEY_ADDR_BIND = (1 << 2),
+ TCP_AUTHOPT_KEY_IFINDEX = (1 << 3),
};

/**
* enum tcp_authopt_alg - Algorithms for TCP Authentication Option
*/
@@ -450,10 +452,19 @@ struct tcp_authopt_key {
* @addr: Key is only valid for this address
*
* Ignored unless TCP_AUTHOPT_KEY_ADDR_BIND flag is set
*/
struct __kernel_sockaddr_storage addr;
+ /**
+ * @ifindex: ifindex of vrf (l3mdev_master) interface
+ *
+ * If the TCP_AUTHOPT_KEY_IFINDEX flag is set then key only applies for
+ * connections through this interface. Interface must be an vrf master.
+ *
+ * This is similar to `tcp_msg5sig.tcpm_ifindex`
+ */
+ int ifindex;
};

/* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */

#define TCP_RECEIVE_ZEROCOPY_FLAG_TLB_CLEAN_HINT 0x1
diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index c00b23f1e5af..3d2c4283923f 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later

#include <net/tcp_authopt.h>
+#include <net/ip.h>
#include <net/ipv6.h>
#include <net/tcp.h>
#include <linux/kref.h>
#include <crypto/hash.h>

@@ -264,10 +265,14 @@ static bool tcp_authopt_key_match_exact(struct tcp_authopt_key_info *info,
{
if (info->send_id != key->send_id)
return false;
if (info->recv_id != key->recv_id)
return false;
+ if ((info->flags & TCP_AUTHOPT_KEY_IFINDEX) != (key->flags & TCP_AUTHOPT_KEY_IFINDEX))
+ return false;
+ if ((info->flags & TCP_AUTHOPT_KEY_IFINDEX) && info->l3index != key->ifindex)
+ return false;
if ((info->flags & TCP_AUTHOPT_KEY_ADDR_BIND) != (key->flags & TCP_AUTHOPT_KEY_ADDR_BIND))
return false;
if (info->flags & TCP_AUTHOPT_KEY_ADDR_BIND)
if (!ipvx_addr_match(&info->addr, &key->addr))
return false;
@@ -333,26 +338,49 @@ static struct tcp_authopt_key_info *tcp_authopt_key_lookup_exact(const struct so
return key_info;

return NULL;
}

+static bool better_key_match(struct tcp_authopt_key_info *old, struct tcp_authopt_key_info *new)
+{
+ if (!old)
+ return true;
+
+ /* l3index always overrides non-l3index */
+ if (old->l3index && new->l3index == 0)
+ return false;
+ if (old->l3index == 0 && new->l3index)
+ return true;
+
+ return false;
+}
+
static struct tcp_authopt_key_info *tcp_authopt_lookup_send(struct netns_tcp_authopt *net,
const struct sock *addr_sk,
int send_id)
{
struct tcp_authopt_key_info *result = NULL;
struct tcp_authopt_key_info *key;
+ int l3index = -1;

hlist_for_each_entry_rcu(key, &net->head, node, 0) {
if (send_id >= 0 && key->send_id != send_id)
continue;
if (key->flags & TCP_AUTHOPT_KEY_ADDR_BIND)
if (!tcp_authopt_key_match_sk_addr(key, addr_sk))
continue;
- if (result && net_ratelimit())
- pr_warn("ambiguous tcp authentication keys configured for send\n");
- result = key;
+ if (key->flags & TCP_AUTHOPT_KEY_IFINDEX) {
+ if (l3index < 0)
+ l3index = l3mdev_master_ifindex_by_index(sock_net(addr_sk),
+ addr_sk->sk_bound_dev_if);
+ if (l3index != key->l3index)
+ continue;
+ }
+ if (better_key_match(result, key))
+ result = key;
+ else if (result)
+ net_warn_ratelimited("ambiguous tcp authentication keys configured for send\n");
}

return result;
}

@@ -583,19 +611,21 @@ int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt)
}

#define TCP_AUTHOPT_KEY_KNOWN_FLAGS ( \
TCP_AUTHOPT_KEY_DEL | \
TCP_AUTHOPT_KEY_EXCLUDE_OPTS | \
- TCP_AUTHOPT_KEY_ADDR_BIND)
+ TCP_AUTHOPT_KEY_ADDR_BIND | \
+ TCP_AUTHOPT_KEY_IFINDEX)

int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
{
struct tcp_authopt_key opt;
struct tcp_authopt_info *info;
struct tcp_authopt_key_info *key_info, *old_key_info;
struct netns_tcp_authopt *net = sock_net_tcp_authopt(sk);
struct tcp_authopt_alg_imp *alg;
+ int l3index = 0;
int err;

sock_owned_by_me(sk);
err = check_sysctl_tcp_authopt();
if (err)
@@ -646,10 +676,24 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
return -EINVAL;
err = tcp_authopt_alg_require(alg);
if (err)
return err;

+ /* check ifindex is valid (zero is always valid) */
+ if (opt.flags & TCP_AUTHOPT_KEY_IFINDEX && opt.ifindex) {
+ struct net_device *dev;
+
+ rcu_read_lock();
+ dev = dev_get_by_index_rcu(sock_net(sk), opt.ifindex);
+ if (dev && netif_is_l3_master(dev))
+ l3index = dev->ifindex;
+ rcu_read_unlock();
+
+ if (!l3index)
+ return -EINVAL;
+ }
+
key_info = sock_kmalloc(sk, sizeof(*key_info), GFP_KERNEL | __GFP_ZERO);
if (!key_info)
return -ENOMEM;
mutex_lock(&net->mutex);
kref_init(&key_info->ref);
@@ -665,10 +709,11 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
key_info->alg_id = opt.alg;
key_info->alg = alg;
key_info->keylen = opt.keylen;
memcpy(key_info->key, opt.key, opt.keylen);
memcpy(&key_info->addr, &opt.addr, sizeof(key_info->addr));
+ key_info->l3index = l3index;
hlist_add_head_rcu(&key_info->node, &net->head);
mutex_unlock(&net->mutex);

return 0;
}
@@ -1455,21 +1500,37 @@ static struct tcp_authopt_key_info *tcp_authopt_lookup_recv(struct sock *sk,
int recv_id,
bool *anykey)
{
struct tcp_authopt_key_info *result = NULL;
struct tcp_authopt_key_info *key;
+ int l3index = -1;

*anykey = false;
/* multiple matches will cause occasional failures */
hlist_for_each_entry_rcu(key, &net->head, node, 0) {
if (key->flags & TCP_AUTHOPT_KEY_ADDR_BIND &&
!tcp_authopt_key_match_skb_addr(key, skb))
continue;
+ if (key->flags & TCP_AUTHOPT_KEY_IFINDEX) {
+ if (l3index < 0) {
+ if (skb->protocol == htons(ETH_P_IP)) {
+ l3index = inet_sdif(skb) ? inet_iif(skb) : 0;
+ } else if (skb->protocol == htons(ETH_P_IPV6)) {
+ l3index = inet6_sdif(skb) ? inet6_iif(skb) : 0;
+ } else {
+ WARN_ONCE(1, "unexpected skb->protocol=%x", skb->protocol);
+ continue;
+ }
+ }
+
+ if (l3index != key->l3index)
+ continue;
+ }
*anykey = true;
if (recv_id >= 0 && key->recv_id != recv_id)
continue;
- if (!result)
+ if (better_key_match(result, key))
result = key;
else if (result)
net_warn_ratelimited("ambiguous tcp authentication keys configured for recv\n");
}

--
2.25.1

2022-01-24 19:08:57

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 18/20] selftests: nettest: Initial tcp_authopt support

Add support for configuring TCP Authentication Option. Only a single key
is supported with default options.

Reviewed-by: David Ahern <[email protected]>
Signed-off-by: Leonard Crestez <[email protected]>
---
tools/testing/selftests/net/nettest.c | 156 ++++++++++++++++++++++++--
1 file changed, 145 insertions(+), 11 deletions(-)

diff --git a/tools/testing/selftests/net/nettest.c b/tools/testing/selftests/net/nettest.c
index 3841e5fec7c7..9615489230f8 100644
--- a/tools/testing/selftests/net/nettest.c
+++ b/tools/testing/selftests/net/nettest.c
@@ -27,10 +27,11 @@
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <getopt.h>
+#include <stdbool.h>

#include <linux/xfrm.h>
#include <linux/ipsec.h>
#include <linux/pfkeyv2.h>

@@ -104,10 +105,12 @@ struct sock_args {
} key_addr;
unsigned int key_addr_prefix_len;
/* 0: default, -1: force off, +1: force on */
int bind_key_ifindex;

+ const char *authopt_password;
+
/* expected addresses and device index for connection */
const char *expected_dev;
const char *expected_server_dev;
int expected_ifindex;

@@ -254,10 +257,75 @@ static int switch_ns(const char *ns)
close(fd);

return ret;
}

+/* Fill key identification fields: address and ifindex */
+static void tcp_authopt_key_fill_id(struct tcp_authopt_key *key, struct sock_args *args)
+{
+ if (args->key_addr_prefix_str) {
+ key->flags |= TCP_AUTHOPT_KEY_ADDR_BIND;
+ switch (args->version) {
+ case AF_INET:
+ memcpy(&key->addr, &args->key_addr.v4, sizeof(args->key_addr.v4));
+ break;
+ case AF_INET6:
+ memcpy(&key->addr, &args->key_addr.v6, sizeof(args->key_addr.v6));
+ break;
+ default:
+ log_error("unknown address family\n");
+ exit(1);
+ }
+ if (args->key_addr_prefix_len) {
+ key->flags |= TCP_AUTHOPT_KEY_PREFIXLEN;
+ key->prefixlen = args->key_addr_prefix_len;
+ }
+ }
+
+ if ((args->ifindex && args->bind_key_ifindex >= 0) || args->bind_key_ifindex >= 1) {
+ key->flags |= TCP_AUTHOPT_KEY_IFINDEX;
+ key->ifindex = args->ifindex;
+ log_msg("TCP_AUTHOPT_KEY_IFINDEX set ifindex=%d\n", key->ifindex);
+ } else {
+ log_msg("TCP_AUTHOPT_KEY_IFINDEX off\n", key->ifindex);
+ }
+}
+
+static int tcp_del_authopt(int sd, struct sock_args *args)
+{
+ struct tcp_authopt_key key;
+ int rc;
+
+ memset(&key, 0, sizeof(key));
+ key.flags |= TCP_AUTHOPT_KEY_DEL;
+ tcp_authopt_key_fill_id(&key, args);
+
+ rc = setsockopt(sd, IPPROTO_TCP, TCP_AUTHOPT_KEY, &key, sizeof(key));
+ if (rc < 0)
+ log_err_errno("setsockopt(TCP_AUTHOPT_KEY) del fail");
+
+ return rc;
+}
+
+static int tcp_set_authopt(int sd, struct sock_args *args)
+{
+ struct tcp_authopt_key key;
+ int rc;
+
+ memset(&key, 0, sizeof(key));
+ strcpy((char *)key.key, args->authopt_password);
+ key.keylen = strlen(args->authopt_password);
+ key.alg = TCP_AUTHOPT_ALG_HMAC_SHA_1_96;
+ tcp_authopt_key_fill_id(&key, args);
+
+ rc = setsockopt(sd, IPPROTO_TCP, TCP_AUTHOPT_KEY, &key, sizeof(key));
+ if (rc < 0)
+ log_err_errno("setsockopt(TCP_AUTHOPT_KEY) add fail");
+
+ return rc;
+}
+
static int tcp_md5sig(int sd, void *addr, socklen_t alen, struct sock_args *args)
{
int keylen = strlen(args->password);
struct tcp_md5sig md5sig = {};
int opt = TCP_MD5SIG;
@@ -1541,10 +1609,15 @@ static int do_server(struct sock_args *args, int ipc_fd)
if (args->password && tcp_md5_remote(lsd, args)) {
close(lsd);
goto err_exit;
}

+ if (args->authopt_password && tcp_set_authopt(lsd, args)) {
+ close(lsd);
+ goto err_exit;
+ }
+
ipc_write(ipc_fd, 1);
while (1) {
log_msg("waiting for client connection.\n");
FD_ZERO(&rfds);
FD_SET(lsd, &rfds);
@@ -1663,10 +1736,13 @@ static int connectsock(void *addr, socklen_t alen, struct sock_args *args)
goto out;

if (args->password && tcp_md5sig(sd, addr, alen, args))
goto err;

+ if (args->authopt_password && tcp_set_authopt(sd, args))
+ goto err;
+
if (args->bind_test_only)
goto out;

if (connect(sd, addr, alen) < 0) {
if (errno != EINPROGRESS) {
@@ -1852,11 +1928,11 @@ static int ipc_parent(int cpid, int fd, struct sock_args *args)

wait(&status);
return client_status;
}

-#define GETOPT_STR "sr:l:c:p:t:g:P:DRn:M:X:m:d:I:BN:O:SCi6xL:0:1:2:3:Fbqf"
+#define GETOPT_STR "sr:l:c:p:t:g:P:DRn:M:X:m:A:d:I:BN:O:SCi6xL:0:1:2:3:Fbqf"
#define OPT_FORCE_BIND_KEY_IFINDEX 1001
#define OPT_NO_BIND_KEY_IFINDEX 1002

static struct option long_opts[] = {
{"force-bind-key-ifindex", 0, 0, OPT_FORCE_BIND_KEY_IFINDEX},
@@ -1897,14 +1973,15 @@ static void print_usage(char *prog)
" -L len send random message of given length\n"
" -n num number of times to send message\n"
"\n"
" -M password use MD5 sum protection\n"
" -X password MD5 password for client mode\n"
- " -m prefix/len prefix and length to use for MD5 key\n"
- " --no-bind-key-ifindex: Force TCP_MD5SIG_FLAG_IFINDEX off\n"
- " --force-bind-key-ifindex: Force TCP_MD5SIG_FLAG_IFINDEX on\n"
+ " -m prefix/len prefix and length to use for MD5/AO key\n"
+ " --no-bind-key-ifindex: Force disable binding key to ifindex\n"
+ " --force-bind-key-ifindex: Force enable binding key to ifindex\n"
" (default: only if -I is passed)\n"
+ " -A password use RFC5925 TCP Authentication Option with password\n"
"\n"
" -g grp multicast group (e.g., 239.1.1.1)\n"
" -i interactive mode (default is echo and terminate)\n"
"\n"
" -0 addr Expected local address\n"
@@ -1915,17 +1992,64 @@ static void print_usage(char *prog)
" -b Bind test only.\n"
" -q Be quiet. Run test without printing anything.\n"
, prog, DEFAULT_PORT);
}

-int main(int argc, char *argv[])
+/* Needs explicit cleanup because keys are global per-namespace */
+void cleanup_tcp_authopt(struct sock_args *args)
+{
+ int fd;
+
+ if (!args->authopt_password)
+ return;
+
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0) {
+ log_err_errno("Failed to create socket");
+ return;
+ }
+ tcp_del_authopt(fd, args);
+ close(fd);
+}
+
+static bool cleanup_done;
+static struct sock_args args = {
+ .version = AF_INET,
+ .type = SOCK_STREAM,
+ .port = DEFAULT_PORT,
+};
+
+void cleanup(void)
+{
+ if (cleanup_done)
+ return;
+ cleanup_done = true;
+ cleanup_tcp_authopt(&args);
+}
+
+void signal_handler(int num)
+{
+ cleanup();
+}
+
+void atexit_handler(void)
+{
+ cleanup();
+}
+
+/* Explicit cleanup is required for TCP-AO because keys are global. */
+static void register_cleanup(void)
{
- struct sock_args args = {
- .version = AF_INET,
- .type = SOCK_STREAM,
- .port = DEFAULT_PORT,
+ struct sigaction sa = {
+ .sa_handler = signal_handler,
};
+ sigaction(SIGINT, &sa, NULL);
+ atexit(atexit_handler);
+}
+
+int main(int argc, char *argv[])
+{
struct protoent *pe;
int both_mode = 0;
unsigned int tmp;
int forever = 0;
int fd[2];
@@ -2022,10 +2146,13 @@ int main(int argc, char *argv[])
args.client_pw = optarg;
break;
case 'm':
args.key_addr_prefix_str = optarg;
break;
+ case 'A':
+ args.authopt_password = optarg;
+ break;
case 'S':
args.use_setsockopt = 1;
break;
case 'f':
args.use_freebind = 1;
@@ -2085,12 +2212,17 @@ int main(int argc, char *argv[])
args.type != SOCK_STREAM)) {
log_error("MD5 passwords apply to TCP only and require a remote ip for the password\n");
return 1;
}

- if (args.key_addr_prefix_str && !args.password) {
- log_error("Prefix range for MD5 protection specified without a password\n");
+ if (args.key_addr_prefix_str && !args.password && !args.authopt_password) {
+ log_error("Prefix range for authentication requires -M or -A\n");
+ return 1;
+ }
+
+ if (args.key_addr_prefix_len && args.authopt_password) {
+ log_error("TCP-AO does not support prefix match, only full address\n");
return 1;
}

if (iter == 0) {
fprintf(stderr, "Invalid number of messages to send\n");
@@ -2113,10 +2245,12 @@ int main(int argc, char *argv[])
fprintf(stderr,
"Local (server mode) or remote IP (client IP) required\n");
return 1;
}

+ register_cleanup();
+
if (interactive) {
prog_timeout = 0;
msg = NULL;
}

--
2.25.1

2022-01-24 19:09:00

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 16/20] tcp: authopt: Add /proc/net/tcp_authopt listing all keys

This provides a very brief summary of all keys for debugging purposes.

Signed-off-by: Leonard Crestez <[email protected]>
---
Documentation/networking/tcp_authopt.rst | 10 +++
net/ipv4/tcp_authopt.c | 102 ++++++++++++++++++++++-
2 files changed, 111 insertions(+), 1 deletion(-)

diff --git a/Documentation/networking/tcp_authopt.rst b/Documentation/networking/tcp_authopt.rst
index 6520c6d02755..eaf389f99139 100644
--- a/Documentation/networking/tcp_authopt.rst
+++ b/Documentation/networking/tcp_authopt.rst
@@ -69,10 +69,20 @@ The rnextkeyid value sent on the wire is usually the recv_id of the current
key used for sending. If the TCP_AUTHOPT_LOCK_RNEXTKEY flag is set in
`tcp_authopt.flags` the value of `tcp_authopt.send_rnextkeyid` is send
instead. This can be used to implement smooth rollover: the peer will switch
its keyid to the received rnextkeyid when it is available.

+Proc interface
+--------------
+
+The ``/proc/net/tcp_authopt`` file contains a tab-separated table of keys. The
+first line contains column names. The number of columns might increase in the
+future if more matching criteria are added. Here is an example of the table::
+
+ flags send_id recv_id alg addr l3index
+ 0x44 0 0 1 10.10.2.2/31 0
+
ABI Reference
=============

.. kernel-doc:: include/uapi/linux/tcp.h
:identifiers: tcp_authopt tcp_authopt_flag tcp_authopt_key tcp_authopt_key_flag tcp_authopt_alg
diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index c4b3c3e0e9ca..5ea93eb495f1 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -5,10 +5,11 @@
#include <net/ipv6.h>
#include <net/tcp.h>
#include <linux/kref.h>
#include <crypto/hash.h>
#include <linux/inetdevice.h>
+#include <linux/proc_fs.h>

/* This is mainly intended to protect against local privilege escalations through
* a rarely used feature so it is deliberately not namespaced.
*/
int sysctl_tcp_authopt;
@@ -1693,26 +1694,125 @@ int __tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb,

return 1;
}
EXPORT_SYMBOL(__tcp_authopt_inbound_check);

+#ifdef CONFIG_PROC_FS
+struct tcp_authopt_iter_state {
+ struct seq_net_private p;
+};
+
+static struct tcp_authopt_key_info *tcp_authopt_get_key_index(struct netns_tcp_authopt *net,
+ int index)
+{
+ struct tcp_authopt_key_info *key;
+
+ hlist_for_each_entry(key, &net->head, node) {
+ if (--index < 0)
+ return key;
+ }
+
+ return NULL;
+}
+
+static void *tcp_authopt_seq_start(struct seq_file *seq, loff_t *pos)
+ __acquires(RCU)
+{
+ struct netns_tcp_authopt *net = &seq_file_net(seq)->tcp_authopt;
+
+ rcu_read_lock();
+ if (*pos == 0)
+ return SEQ_START_TOKEN;
+ else
+ return tcp_authopt_get_key_index(net, *pos - 1);
+}
+
+static void tcp_authopt_seq_stop(struct seq_file *seq, void *v)
+ __releases(RCU)
+{
+ rcu_read_unlock();
+}
+
+static void *tcp_authopt_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+ struct netns_tcp_authopt *net = &seq_file_net(seq)->tcp_authopt;
+ void *ret;
+
+ ret = tcp_authopt_get_key_index(net, *pos);
+ ++*pos;
+
+ return ret;
+}
+
+static int tcp_authopt_seq_show(struct seq_file *seq, void *v)
+{
+ struct tcp_authopt_key_info *key = v;
+
+ /* FIXME: Document somewhere */
+ /* Key is deliberately inaccessible */
+ if (v == SEQ_START_TOKEN) {
+ seq_puts(seq, "flags\tsend_id\trecv_id\talg\taddr\tl3index\n");
+ return 0;
+ }
+
+ seq_printf(seq, "0x%x\t%d\t%d\t%d",
+ key->flags, key->send_id, key->recv_id, (int)key->alg_id);
+ if (key->flags & TCP_AUTHOPT_KEY_ADDR_BIND) {
+ if (key->addr.ss_family == AF_INET6)
+ seq_printf(seq, "\t%pI6", &((struct sockaddr_in6 *)&key->addr)->sin6_addr);
+ else
+ seq_printf(seq, "\t%pI4", &((struct sockaddr_in *)&key->addr)->sin_addr);
+ if (key->flags & TCP_AUTHOPT_KEY_PREFIXLEN)
+ seq_printf(seq, "/%d", key->prefixlen);
+ } else {
+ seq_puts(seq, "\t*");
+ }
+ seq_printf(seq, "\t%d", key->l3index);
+ seq_puts(seq, "\n");
+
+ return 0;
+}
+
+static const struct seq_operations tcp_authopt_seq_ops = {
+ .start = tcp_authopt_seq_start,
+ .next = tcp_authopt_seq_next,
+ .stop = tcp_authopt_seq_stop,
+ .show = tcp_authopt_seq_show,
+};
+#endif /* CONFIG_PROC_FS */
+
+static int __net_init tcp_authopt_proc_init_net(struct net *net)
+{
+ if (!proc_create_net("tcp_authopt", 0400, net->proc_net,
+ &tcp_authopt_seq_ops,
+ sizeof(struct tcp_authopt_iter_state)))
+ return -ENOMEM;
+ return 0;
+}
+
+static void __net_exit tcp_authopt_proc_exit_net(struct net *net)
+{
+ remove_proc_entry("tcp_authopt", net->proc_net);
+}
+
static int tcp_authopt_init_net(struct net *full_net)
{
struct netns_tcp_authopt *net = &full_net->tcp_authopt;

mutex_init(&net->mutex);
INIT_HLIST_HEAD(&net->head);

- return 0;
+ return tcp_authopt_proc_init_net(full_net);
}

static void tcp_authopt_exit_net(struct net *full_net)
{
struct netns_tcp_authopt *net = &full_net->tcp_authopt;
struct tcp_authopt_key_info *key;
struct hlist_node *n;

+ tcp_authopt_proc_exit_net(full_net);
mutex_lock(&net->mutex);

hlist_for_each_entry_safe(key, n, &net->head, node) {
hlist_del_rcu(&key->node);
tcp_authopt_key_put(key);
--
2.25.1

2022-01-24 19:09:06

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 17/20] selftests: nettest: Rename md5_prefix to key_addr_prefix

This is in preparation for reusing the same option for TCP-AO

Reviewed-by: David Ahern <[email protected]>
Signed-off-by: Leonard Crestez <[email protected]>
---
tools/testing/selftests/net/nettest.c | 50 +++++++++++++--------------
1 file changed, 25 insertions(+), 25 deletions(-)

diff --git a/tools/testing/selftests/net/nettest.c b/tools/testing/selftests/net/nettest.c
index d9a6fd2cd9d3..3841e5fec7c7 100644
--- a/tools/testing/selftests/net/nettest.c
+++ b/tools/testing/selftests/net/nettest.c
@@ -94,17 +94,17 @@ struct sock_args {
const char *clientns;
const char *serverns;

const char *password;
const char *client_pw;
- /* prefix for MD5 password */
- const char *md5_prefix_str;
+ /* prefix for MD5/AO*/
+ const char *key_addr_prefix_str;
union {
struct sockaddr_in v4;
struct sockaddr_in6 v6;
- } md5_prefix;
- unsigned int prefix_len;
+ } key_addr;
+ unsigned int key_addr_prefix_len;
/* 0: default, -1: force off, +1: force on */
int bind_key_ifindex;

/* expected addresses and device index for connection */
const char *expected_dev;
@@ -264,16 +264,16 @@ static int tcp_md5sig(int sd, void *addr, socklen_t alen, struct sock_args *args
int rc;

md5sig.tcpm_keylen = keylen;
memcpy(md5sig.tcpm_key, args->password, keylen);

- if (args->prefix_len) {
+ if (args->key_addr_prefix_len) {
opt = TCP_MD5SIG_EXT;
md5sig.tcpm_flags |= TCP_MD5SIG_FLAG_PREFIX;

- md5sig.tcpm_prefixlen = args->prefix_len;
- addr = &args->md5_prefix;
+ md5sig.tcpm_prefixlen = args->key_addr_prefix_len;
+ addr = &args->key_addr;
}
memcpy(&md5sig.tcpm_addr, addr, alen);

if ((args->ifindex && args->bind_key_ifindex >= 0) || args->bind_key_ifindex >= 1) {
opt = TCP_MD5SIG_EXT;
@@ -309,17 +309,17 @@ static int tcp_md5_remote(int sd, struct sock_args *args)
int alen;

switch (args->version) {
case AF_INET:
sin.sin_port = htons(args->port);
- sin.sin_addr = args->md5_prefix.v4.sin_addr;
+ sin.sin_addr = args->key_addr.v4.sin_addr;
addr = &sin;
alen = sizeof(sin);
break;
case AF_INET6:
sin6.sin6_port = htons(args->port);
- sin6.sin6_addr = args->md5_prefix.v6.sin6_addr;
+ sin6.sin6_addr = args->key_addr.v6.sin6_addr;
addr = &sin6;
alen = sizeof(sin6);
break;
default:
log_error("unknown address family\n");
@@ -705,11 +705,11 @@ enum addr_type {
ADDR_TYPE_LOCAL,
ADDR_TYPE_REMOTE,
ADDR_TYPE_MCAST,
ADDR_TYPE_EXPECTED_LOCAL,
ADDR_TYPE_EXPECTED_REMOTE,
- ADDR_TYPE_MD5_PREFIX,
+ ADDR_TYPE_KEY_PREFIX,
};

static int convert_addr(struct sock_args *args, const char *_str,
enum addr_type atype)
{
@@ -745,32 +745,32 @@ static int convert_addr(struct sock_args *args, const char *_str,
break;
case ADDR_TYPE_EXPECTED_REMOTE:
desc = "expected remote";
addr = &args->expected_raddr;
break;
- case ADDR_TYPE_MD5_PREFIX:
- desc = "md5 prefix";
+ case ADDR_TYPE_KEY_PREFIX:
+ desc = "key addr prefix";
if (family == AF_INET) {
- args->md5_prefix.v4.sin_family = AF_INET;
- addr = &args->md5_prefix.v4.sin_addr;
+ args->key_addr.v4.sin_family = AF_INET;
+ addr = &args->key_addr.v4.sin_addr;
} else if (family == AF_INET6) {
- args->md5_prefix.v6.sin6_family = AF_INET6;
- addr = &args->md5_prefix.v6.sin6_addr;
+ args->key_addr.v6.sin6_family = AF_INET6;
+ addr = &args->key_addr.v6.sin6_addr;
} else
return 1;

sep = strchr(str, '/');
if (sep) {
*sep = '\0';
sep++;
if (str_to_uint(sep, 1, pfx_len_max,
- &args->prefix_len) != 0) {
- fprintf(stderr, "Invalid port\n");
+ &args->key_addr_prefix_len) != 0) {
+ fprintf(stderr, "Invalid prefix\n");
return 1;
}
} else {
- args->prefix_len = 0;
+ args->key_addr_prefix_len = 0;
}
break;
default:
log_error("unknown address type\n");
exit(1);
@@ -835,13 +835,13 @@ static int validate_addresses(struct sock_args *args)

if (args->remote_addr_str &&
convert_addr(args, args->remote_addr_str, ADDR_TYPE_REMOTE) < 0)
return 1;

- if (args->md5_prefix_str &&
- convert_addr(args, args->md5_prefix_str,
- ADDR_TYPE_MD5_PREFIX) < 0)
+ if (args->key_addr_prefix_str &&
+ convert_addr(args, args->key_addr_prefix_str,
+ ADDR_TYPE_KEY_PREFIX) < 0)
return 1;

if (args->expected_laddr_str &&
convert_addr(args, args->expected_laddr_str,
ADDR_TYPE_EXPECTED_LOCAL))
@@ -2020,11 +2020,11 @@ int main(int argc, char *argv[])
break;
case 'X':
args.client_pw = optarg;
break;
case 'm':
- args.md5_prefix_str = optarg;
+ args.key_addr_prefix_str = optarg;
break;
case 'S':
args.use_setsockopt = 1;
break;
case 'f':
@@ -2079,17 +2079,17 @@ int main(int argc, char *argv[])
return 1;
}
}

if (args.password &&
- ((!args.has_remote_ip && !args.md5_prefix_str) ||
+ ((!args.has_remote_ip && !args.key_addr_prefix_str) ||
args.type != SOCK_STREAM)) {
log_error("MD5 passwords apply to TCP only and require a remote ip for the password\n");
return 1;
}

- if (args.md5_prefix_str && !args.password) {
+ if (args.key_addr_prefix_str && !args.password) {
log_error("Prefix range for MD5 protection specified without a password\n");
return 1;
}

if (iter == 0) {
--
2.25.1

2022-01-24 19:09:09

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 10/20] tcp: authopt: Add support for signing skb-less replies

This is required because tcp ipv4 sometimes sends replies without
allocating a full skb that can be signed by tcp authopt.

Handle this with additional code in tcp authopt.

Signed-off-by: Leonard Crestez <[email protected]>
---
include/net/tcp_authopt.h | 7 ++
net/ipv4/tcp_authopt.c | 144 ++++++++++++++++++++++++++++++++++++++
2 files changed, 151 insertions(+)

diff --git a/include/net/tcp_authopt.h b/include/net/tcp_authopt.h
index 6e9b5ca22f62..9ee5165388b1 100644
--- a/include/net/tcp_authopt.h
+++ b/include/net/tcp_authopt.h
@@ -115,10 +115,17 @@ static inline struct tcp_authopt_key_info *tcp_authopt_select_key(
int tcp_authopt_hash(
char *hash_location,
struct tcp_authopt_key_info *key,
struct tcp_authopt_info *info,
struct sock *sk, struct sk_buff *skb);
+int tcp_v4_authopt_hash_reply(
+ char *hash_location,
+ struct tcp_authopt_info *info,
+ struct tcp_authopt_key_info *key,
+ __be32 saddr,
+ __be32 daddr,
+ struct tcphdr *th);
int __tcp_authopt_openreq(struct sock *newsk, const struct sock *oldsk, struct request_sock *req);
static inline int tcp_authopt_openreq(
struct sock *newsk,
const struct sock *oldsk,
struct request_sock *req)
diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index 3c05b6c55191..7a2e15dd80ba 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -933,10 +933,72 @@ static int tcp_authopt_get_traffic_key(struct sock *sk,
out:
tcp_authopt_put_kdf_pool(key, pool);
return err;
}

+struct tcp_v4_authopt_context_data {
+ __be32 saddr;
+ __be32 daddr;
+ __be16 sport;
+ __be16 dport;
+ __be32 sisn;
+ __be32 disn;
+ __be16 digestbits;
+} __packed;
+
+static int tcp_v4_authopt_get_traffic_key_noskb(struct tcp_authopt_key_info *key,
+ __be32 saddr,
+ __be32 daddr,
+ __be16 sport,
+ __be16 dport,
+ __be32 sisn,
+ __be32 disn,
+ u8 *traffic_key)
+{
+ int err;
+ struct tcp_authopt_alg_pool *pool;
+ struct tcp_v4_authopt_context_data data;
+
+ BUILD_BUG_ON(sizeof(data) != 22);
+
+ pool = tcp_authopt_get_kdf_pool(key);
+ if (IS_ERR(pool))
+ return PTR_ERR(pool);
+
+ err = tcp_authopt_setkey(pool, key);
+ if (err)
+ goto out;
+ err = crypto_ahash_init(pool->req);
+ if (err)
+ goto out;
+
+ // RFC5926 section 3.1.1.1
+ // Separate to keep alignment semi-sane
+ err = crypto_ahash_buf(pool->req, "\x01TCP-AO", 7);
+ if (err)
+ return err;
+ data.saddr = saddr;
+ data.daddr = daddr;
+ data.sport = sport;
+ data.dport = dport;
+ data.sisn = sisn;
+ data.disn = disn;
+ data.digestbits = htons(crypto_ahash_digestsize(pool->tfm) * 8);
+
+ err = crypto_ahash_buf(pool->req, (u8 *)&data, sizeof(data));
+ if (err)
+ goto out;
+ ahash_request_set_crypt(pool->req, NULL, traffic_key, 0);
+ err = crypto_ahash_final(pool->req);
+ if (err)
+ goto out;
+
+out:
+ tcp_authopt_put_kdf_pool(key, pool);
+ return err;
+}
+
static int crypto_ahash_buf_zero(struct ahash_request *req, int len)
{
u8 zeros[TCP_AUTHOPT_MACLEN] = {0};
int buflen, err;

@@ -1203,10 +1265,92 @@ int tcp_authopt_hash(char *hash_location,
return err;
}
EXPORT_SYMBOL(tcp_authopt_hash);

/**
+ * tcp_v4_authopt_hash_reply - Hash tcp+ipv4 header without SKB
+ *
+ * @hash_location: output buffer
+ * @info: sending socket's tcp_authopt_info
+ * @key: signing key, from tcp_authopt_select_key.
+ * @saddr: source address
+ * @daddr: destination address
+ * @th: Pointer to TCP header and options
+ */
+int tcp_v4_authopt_hash_reply(char *hash_location,
+ struct tcp_authopt_info *info,
+ struct tcp_authopt_key_info *key,
+ __be32 saddr,
+ __be32 daddr,
+ struct tcphdr *th)
+{
+ struct tcp_authopt_alg_pool *pool;
+ u8 macbuf[TCP_AUTHOPT_MAXMACBUF];
+ u8 traffic_key[TCP_AUTHOPT_MAX_TRAFFIC_KEY_LEN];
+ __be32 sne = 0;
+ int err;
+
+ /* Call special code path for computing traffic key without skb
+ * This can be called from tcp_v4_reqsk_send_ack so caching would be
+ * difficult here.
+ */
+ err = tcp_v4_authopt_get_traffic_key_noskb(key, saddr, daddr,
+ th->source, th->dest,
+ htonl(info->src_isn), htonl(info->dst_isn),
+ traffic_key);
+ if (err)
+ goto out_err_traffic_key;
+
+ /* Init mac shash */
+ pool = tcp_authopt_get_mac_pool(key);
+ if (IS_ERR(pool))
+ return PTR_ERR(pool);
+ err = crypto_ahash_setkey(pool->tfm, traffic_key, key->alg->traffic_key_len);
+ if (err)
+ goto out_err;
+ err = crypto_ahash_init(pool->req);
+ if (err)
+ return err;
+
+ err = crypto_ahash_buf(pool->req, (u8 *)&sne, 4);
+ if (err)
+ return err;
+
+ err = tcp_authopt_hash_tcp4_pseudoheader(pool, saddr, daddr, th->doff * 4);
+ if (err)
+ return err;
+
+ // TCP header with checksum set to zero. Caller ensures this.
+ if (WARN_ON_ONCE(th->check != 0))
+ goto out_err;
+ err = crypto_ahash_buf(pool->req, (u8 *)th, sizeof(*th));
+ if (err)
+ goto out_err;
+
+ // TCP options
+ err = tcp_authopt_hash_opts(pool, th, (struct tcphdr_authopt *)(hash_location - 4),
+ !(key->flags & TCP_AUTHOPT_KEY_EXCLUDE_OPTS));
+ if (err)
+ goto out_err;
+
+ ahash_request_set_crypt(pool->req, NULL, macbuf, 0);
+ err = crypto_ahash_final(pool->req);
+ if (err)
+ goto out_err;
+ memcpy(hash_location, macbuf, TCP_AUTHOPT_MACLEN);
+
+ tcp_authopt_put_mac_pool(key, pool);
+ return 0;
+
+out_err:
+ tcp_authopt_put_mac_pool(key, pool);
+out_err_traffic_key:
+ memset(hash_location, 0, TCP_AUTHOPT_MACLEN);
+ return err;
+}
+
+/*
* tcp_authopt_lookup_recv - lookup key for receive
*
* @sk: Receive socket
* @skb: Packet, used to compare addr and iface
* @net: Per-namespace information containing keys
--
2.25.1

2022-01-24 19:09:11

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 19/20] selftests: net/fcnal: Initial tcp_authopt support

Tests are mostly copied from tcp_md5 with minor changes.

It covers VRF support but only based on binding multiple servers: not
multiple keys bound to different interfaces.

Also add a specific -t tcp_authopt to run only these tests specifically.

Signed-off-by: Leonard Crestez <[email protected]>
---
tools/testing/selftests/net/fcnal-test.sh | 329 +++++++++++++++++++++-
1 file changed, 327 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/net/fcnal-test.sh b/tools/testing/selftests/net/fcnal-test.sh
index 412d85205546..610ae75df8b2 100755
--- a/tools/testing/selftests/net/fcnal-test.sh
+++ b/tools/testing/selftests/net/fcnal-test.sh
@@ -817,10 +817,330 @@ ipv4_ping()
}

################################################################################
# IPv4 TCP

+#
+# TCP Authentication Option Tests
+#
+
+# try to enable tcp_authopt sysctl
+enable_tcp_authopt()
+{
+ if [[ -e /proc/sys/net/ipv4/tcp_authopt ]]; then
+ sysctl -w net.ipv4.tcp_authopt=1
+ fi
+}
+
+# check if tcp_authopt is compiled with a client-side bind test
+has_tcp_authopt()
+{
+ run_cmd_nsb nettest -b -A ${MD5_PW} -r ${NSA_IP}
+}
+
+# Verify /proc/net/tcp_authopt is empty in all namespaces
+check_tcp_authopt_key_leak()
+{
+ local ns cnt
+
+ for ns in $NSA $NSB $NSC; do
+ if ! ip netns list | grep -q $ns; then
+ continue
+ fi
+ cnt=$(ip netns exec "$ns" cat /proc/net/tcp_authopt | wc -l)
+ if [[ $cnt != 1 ]]; then
+ echo "FAIL: leaked tcp_authopt keys in netns $ns"
+ ip netns exec $ns cat /proc/net/tcp_authopt
+ return 1
+ fi
+ done
+}
+
+log_check_tcp_authopt_key_leak()
+{
+ check_tcp_authopt_key_leak
+ log_test $? 0 "TCP-AO: Key leak check"
+}
+
+ipv4_tcp_authopt_novrf()
+{
+ enable_tcp_authopt
+ if ! has_tcp_authopt; then
+ echo "TCP-AO appears to be missing, skip"
+ return 0
+ fi
+
+ log_start
+ run_cmd nettest -s -A ${MD5_PW} -m ${NSB_IP} &
+ sleep 1
+ run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_PW}
+ log_test $? 0 "AO: Single address config"
+
+ log_start
+ run_cmd nettest -s &
+ sleep 1
+ run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_PW}
+ log_test $? 2 "AO: Server no config, client uses password"
+
+ log_start
+ run_cmd nettest -s -A ${MD5_PW} -m ${NSB_IP} &
+ sleep 1
+ run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_WRONG_PW}
+ log_test $? 2 "AO: Client uses wrong password"
+ log_check_tcp_authopt_key_leak
+
+ log_start
+ run_cmd nettest -s -A ${MD5_PW} -m ${NSB_LO_IP} &
+ sleep 1
+ run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_PW}
+ log_test $? 2 "AO: Client address does not match address configured on server"
+ log_check_tcp_authopt_key_leak
+
+ # client in prefix
+ log_start
+ run_cmd nettest -s -A ${MD5_PW} -m ${NS_NET} &
+ sleep 1
+ run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_PW}
+ log_test $? 0 "AO: Prefix config"
+
+ # client in prefix, wrong password
+ log_start
+ show_hint "Should timeout since client uses wrong password"
+ run_cmd nettest -s -A ${MD5_PW} -m ${NS_NET} &
+ sleep 1
+ run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_WRONG_PW}
+ log_test $? 2 "AO: Prefix config, client uses wrong password"
+ log_check_tcp_authopt_key_leak
+
+ # client outside of prefix
+ log_start
+ show_hint "Should timeout due to MD5 mismatch"
+ run_cmd nettest -s -A ${MD5_PW} -m ${NS_NET} &
+ sleep 1
+ run_cmd_nsb nettest -c ${NSB_LO_IP} -r ${NSA_IP} -A ${MD5_PW}
+ log_test $? 2 "AO: Prefix config, client address not in configured prefix"
+ log_check_tcp_authopt_key_leak
+}
+
+ipv6_tcp_authopt_novrf()
+{
+ enable_tcp_authopt
+ if ! has_tcp_authopt; then
+ echo "TCP-AO appears to be missing, skip"
+ return 0
+ fi
+
+ log_start
+ run_cmd nettest -6 -s -A ${MD5_PW} &
+ sleep 1
+ run_cmd_nsb nettest -6 -r ${NSA_IP6} -A ${MD5_PW}
+ log_test $? 0 "AO: Simple correct config"
+
+ log_start
+ run_cmd nettest -6 -s
+ sleep 1
+ run_cmd_nsb nettest -6 -r ${NSA_IP6} -A ${MD5_PW}
+ log_test $? 2 "AO: Server no config, client uses password"
+
+ log_start
+ run_cmd nettest -6 -s -A ${MD5_PW} -m ${NSB_IP6} &
+ sleep 1
+ run_cmd_nsb nettest -6 -r ${NSA_IP6} -A ${MD5_WRONG_PW}
+ log_test $? 2 "AO: Client uses wrong password"
+
+ log_start
+ run_cmd nettest -6 -s -A ${MD5_PW} -m ${NSB_LO_IP6} &
+ sleep 1
+ run_cmd_nsb nettest -6 -r ${NSA_IP6} -A ${MD5_PW}
+ log_test $? 2 "AO: Client address does not match address configured on server"
+}
+
+ipv4_tcp_authopt_vrf()
+{
+ enable_tcp_authopt
+ if ! has_tcp_authopt; then
+ echo "TCP-AO appears to be missing, skip"
+ return 0
+ fi
+
+ log_start
+ run_cmd nettest -s -I ${VRF} -A ${MD5_PW} &
+ sleep 1
+ run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_PW}
+ log_test $? 0 "AO: VRF: Simple config"
+
+ #
+ # duplicate config between default VRF and a VRF
+ #
+
+ log_start
+ run_cmd nettest -s -I ${VRF} -A ${MD5_PW} -m ${NSB_IP} &
+ run_cmd nettest -s -A ${MD5_WRONG_PW} -m ${NSB_IP} &
+ sleep 1
+ run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_PW}
+ log_test $? 0 "AO: VRF: Servers in default VRF and VRF, client in VRF"
+
+ log_start
+ run_cmd nettest -s -I ${VRF} -A ${MD5_PW} -m ${NSB_IP} &
+ run_cmd nettest -s -A ${MD5_WRONG_PW} -m ${NSB_IP} &
+ sleep 1
+ run_cmd_nsc nettest -r ${NSA_IP} -A ${MD5_WRONG_PW}
+ log_test $? 0 "AO: VRF: Servers in default VRF and VRF, client in default VRF"
+
+ log_start
+ show_hint "Should timeout since client in default VRF uses VRF password"
+ run_cmd nettest -s -I ${VRF} -A ${MD5_PW} -m ${NSB_IP} &
+ run_cmd nettest -s -A ${MD5_WRONG_PW} -m ${NSB_IP} &
+ sleep 1
+ run_cmd_nsc nettest -r ${NSA_IP} -A ${MD5_PW}
+ log_test $? 2 "AO: VRF: Servers in default VRF and VRF, conn in default VRF with VRF pw"
+
+ log_start
+ show_hint "Should timeout since client in VRF uses default VRF password"
+ run_cmd nettest -s -I ${VRF} -A ${MD5_PW} -m ${NSB_IP} &
+ run_cmd nettest -s -A ${MD5_WRONG_PW} -m ${NSB_IP} &
+ sleep 1
+ run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_WRONG_PW}
+ log_test $? 2 "AO: VRF: Servers in default VRF and VRF, conn in VRF with default VRF pw"
+
+ test_ipv4_tcp_authopt_vrf__global_server__bind_ifindex0
+}
+
+test_ipv4_tcp_authopt_vrf__global_server__bind_ifindex0()
+{
+ # This particular test needs tcp_l3mdev_accept=1 for Global server to accept VRF connections
+ local old_tcp_l3mdev_accept
+ old_tcp_l3mdev_accept=$(get_sysctl net.ipv4.tcp_l3mdev_accept)
+ set_sysctl net.ipv4.tcp_l3mdev_accept=1
+
+ log_start
+ run_cmd nettest -s -A ${MD5_PW} --force-bind-key-ifindex &
+ sleep 1
+ run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_PW}
+ log_test $? 2 "AO: VRF: Global server, Key bound to ifindex=0 rejects VRF connection"
+
+ log_start
+ run_cmd nettest -s -A ${MD5_PW} --force-bind-key-ifindex &
+ sleep 1
+ run_cmd_nsc nettest -r ${NSA_IP} -A ${MD5_PW}
+ log_test $? 0 "AO: VRF: Global server, key bound to ifindex=0 accepts non-VRF connection"
+ log_start
+
+ run_cmd nettest -s -A ${MD5_PW} --no-bind-key-ifindex &
+ sleep 1
+ run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_PW}
+ log_test $? 0 "AO: VRF: Global server, key not bound to ifindex accepts VRF connection"
+
+ log_start
+ run_cmd nettest -s -A ${MD5_PW} --no-bind-key-ifindex &
+ sleep 1
+ run_cmd_nsc nettest -r ${NSA_IP} -A ${MD5_PW}
+ log_test $? 0 "AO: VRF: Global server, key not bound to ifindex accepts non-VRF connection"
+
+ # restore value
+ set_sysctl net.ipv4.tcp_l3mdev_accept="$old_tcp_l3mdev_accept"
+}
+
+ipv6_tcp_authopt_vrf()
+{
+ enable_tcp_authopt
+ if ! has_tcp_authopt; then
+ echo "TCP-AO appears to be missing, skip"
+ return 0
+ fi
+
+ log_start
+ run_cmd nettest -6 -s -I ${VRF} -A ${MD5_PW} &
+ sleep 1
+ run_cmd_nsb nettest -6 -r ${NSA_IP6} -A ${MD5_PW}
+ log_test $? 0 "AO: VRF: Simple config"
+
+ #
+ # duplicate config between default VRF and a VRF
+ #
+
+ log_start
+ run_cmd nettest -6 -s -I ${VRF} -A ${MD5_PW} -m ${NSB_IP6} &
+ run_cmd nettest -6 -s -A ${MD5_WRONG_PW} -m ${NSB_IP6} &
+ sleep 1
+ run_cmd_nsb nettest -6 -r ${NSA_IP6} -A ${MD5_PW}
+ log_test $? 0 "AO: VRF: Servers in default VRF and VRF, client in VRF"
+
+ log_start
+ run_cmd nettest -6 -s -I ${VRF} -A ${MD5_PW} -m ${NSB_IP6} &
+ run_cmd nettest -6 -s -A ${MD5_WRONG_PW} -m ${NSB_IP6} &
+ sleep 1
+ run_cmd_nsc nettest -6 -r ${NSA_IP6} -A ${MD5_WRONG_PW}
+ log_test $? 0 "AO: VRF: Servers in default VRF and VRF, client in default VRF"
+
+ log_start
+ show_hint "Should timeout since client in default VRF uses VRF password"
+ run_cmd nettest -6 -s -I ${VRF} -A ${MD5_PW} -m ${NSB_IP6} &
+ run_cmd nettest -6 -s -A ${MD5_WRONG_PW} -m ${NSB_IP6} &
+ sleep 1
+ run_cmd_nsc nettest -6 -r ${NSA_IP6} -A ${MD5_PW}
+ log_test $? 2 "AO: VRF: Servers in default VRF and VRF, conn in default VRF with VRF pw"
+
+ log_start
+ show_hint "Should timeout since client in VRF uses default VRF password"
+ run_cmd nettest -6 -s -I ${VRF} -A ${MD5_PW} -m ${NSB_IP6} &
+ run_cmd nettest -6 -s -A ${MD5_WRONG_PW} -m ${NSB_IP6} &
+ sleep 1
+ run_cmd_nsb nettest -6 -r ${NSA_IP6} -A ${MD5_WRONG_PW}
+ log_test $? 2 "AO: VRF: Servers in default VRF and VRF, conn in VRF with default VRF pw"
+
+ log_start
+ run_cmd nettest -6 -s -I ${VRF} -A ${MD5_PW} -m ${NS_NET6} &
+ run_cmd nettest -6 -s -A ${MD5_WRONG_PW} -m ${NS_NET6} &
+ sleep 1
+ run_cmd_nsb nettest -6 -r ${NSA_IP6} -A ${MD5_PW}
+ log_test $? 0 "AO: VRF: Prefix config in default VRF and VRF, conn in VRF"
+
+ log_start
+ run_cmd nettest -6 -s -I ${VRF} -A ${MD5_PW} -m ${NS_NET6} &
+ run_cmd nettest -6 -s -A ${MD5_WRONG_PW} -m ${NS_NET6} &
+ sleep 1
+ run_cmd_nsc nettest -6 -r ${NSA_IP6} -A ${MD5_WRONG_PW}
+ log_test $? 0 "AO: VRF: Prefix config in default VRF and VRF, conn in default VRF"
+
+ log_start
+ show_hint "Should timeout since client in default VRF uses VRF password"
+ run_cmd nettest -6 -s -I ${VRF} -A ${MD5_PW} -m ${NS_NET6} &
+ run_cmd nettest -6 -s -A ${MD5_WRONG_PW} -m ${NS_NET6} &
+ sleep 1
+ run_cmd_nsc nettest -6 -r ${NSA_IP6} -A ${MD5_PW}
+ log_test $? 2 "AO: VRF: Prefix config in def VRF and VRF, conn in def VRF with VRF pw"
+
+ log_start
+ show_hint "Should timeout since client in VRF uses default VRF password"
+ run_cmd nettest -6 -s -I ${VRF} -A ${MD5_PW} -m ${NS_NET6} &
+ run_cmd nettest -6 -s -A ${MD5_WRONG_PW} -m ${NS_NET6} &
+ sleep 1
+ run_cmd_nsb nettest -6 -r ${NSA_IP6} -A ${MD5_WRONG_PW}
+ log_test $? 2 "AO: VRF: Prefix config in dev VRF and VRF, conn in VRF with def VRF pw"
+}
+
+only_tcp_authopt()
+{
+ log_section "TCP Authentication Option"
+
+ setup
+ set_sysctl net.ipv4.tcp_l3mdev_accept=0
+ log_subsection "TCP-AO IPv4 no VRF"
+ ipv4_tcp_authopt_novrf
+ log_subsection "TCP-AO IPv6 no VRF"
+ ipv6_tcp_authopt_novrf
+
+ setup "yes"
+ setup_vrf_dup
+ set_sysctl net.ipv4.tcp_l3mdev_accept=0
+ log_subsection "TCP-AO IPv4 VRF"
+ ipv4_tcp_authopt_vrf
+ log_subsection "TCP-AO IPv6 VRF"
+ ipv6_tcp_authopt_vrf
+}
+
#
# MD5 tests without VRF
#
ipv4_tcp_md5_novrf()
{
@@ -1202,10 +1522,11 @@ ipv4_tcp_novrf()
show_hint "Should fail 'Connection refused'"
run_cmd nettest -d ${NSA_DEV} -r ${a}
log_test_addr ${a} $? 1 "No server, device client, local conn"

ipv4_tcp_md5_novrf
+ ipv4_tcp_authopt_novrf
}

ipv4_tcp_vrf()
{
local a
@@ -1254,13 +1575,14 @@ ipv4_tcp_vrf()
run_cmd nettest -s &
sleep 1
run_cmd nettest -r ${a} -d ${NSA_DEV}
log_test_addr ${a} $? 1 "Global server, local connection"

- # run MD5 tests
+ # run MD5+AO tests
setup_vrf_dup
ipv4_tcp_md5
+ ipv6_tcp_md5_vrf
cleanup_vrf_dup

#
# enable VRF global server
#
@@ -2685,10 +3007,11 @@ ipv6_tcp_novrf()
run_cmd nettest -6 -d ${NSA_DEV} -r ${a}
log_test_addr ${a} $? 1 "No server, device client, local conn"
done

ipv6_tcp_md5_novrf
+ ipv6_tcp_authopt_novrf
}

ipv6_tcp_vrf()
{
local a
@@ -2753,13 +3076,14 @@ ipv6_tcp_vrf()
run_cmd nettest -6 -s &
sleep 1
run_cmd nettest -6 -r ${a} -d ${NSA_DEV}
log_test_addr ${a} $? 1 "Global server, local connection"

- # run MD5 tests
+ # run MD5+AO tests
setup_vrf_dup
ipv6_tcp_md5
+ ipv6_tcp_authopt_vrf
cleanup_vrf_dup

#
# enable VRF global server
#
@@ -4125,10 +4449,11 @@ do
ipv6_bind|bind6) ipv6_addr_bind;;
ipv6_runtime) ipv6_runtime;;
ipv6_netfilter) ipv6_netfilter;;

use_cases) use_cases;;
+ tcp_authopt) only_tcp_authopt;;

# setup namespaces and config, but do not run any tests
setup) setup; exit 0;;
vrf_setup) setup "yes"; exit 0;;
esac
--
2.25.1

2022-01-24 19:09:21

by Leonard Crestez

[permalink] [raw]
Subject: [PATCH v5 14/20] tcp: authopt: Add NOSEND/NORECV flags

Add flags to allow marking individual keys and invalid for send or recv.
Making keys assymetric this way is not mentioned in RFC5925 but RFC8177
requires that keys inside a keychain have independent "accept" and
"send" lifetimes.

Flag names are negative so that the default behavior is for keys to be
valid for both send and recv.

Setting both NOSEND and NORECV for a certain peer address can be used on
a listen socket can be used to mean "TCP-AO is required from this peer
but no keys are currently valid".

Signed-off-by: Leonard Crestez <[email protected]>
---
include/uapi/linux/tcp.h | 4 ++++
net/ipv4/tcp_authopt.c | 9 ++++++++-
2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h
index a7f5f918ed5a..ed27feb93b0e 100644
--- a/include/uapi/linux/tcp.h
+++ b/include/uapi/linux/tcp.h
@@ -401,16 +401,20 @@ struct tcp_authopt {
*
* @TCP_AUTHOPT_KEY_DEL: Delete the key and ignore non-id fields
* @TCP_AUTHOPT_KEY_EXCLUDE_OPTS: Exclude TCP options from signature
* @TCP_AUTHOPT_KEY_ADDR_BIND: Key only valid for `tcp_authopt.addr`
* @TCP_AUTHOPT_KEY_IFINDEX: Key only valid for `tcp_authopt.ifindex`
+ * @TCP_AUTHOPT_KEY_NOSEND: Key invalid for send (expired)
+ * @TCP_AUTHOPT_KEY_NORECV: Key invalid for recv (expired)
*/
enum tcp_authopt_key_flag {
TCP_AUTHOPT_KEY_DEL = (1 << 0),
TCP_AUTHOPT_KEY_EXCLUDE_OPTS = (1 << 1),
TCP_AUTHOPT_KEY_ADDR_BIND = (1 << 2),
TCP_AUTHOPT_KEY_IFINDEX = (1 << 3),
+ TCP_AUTHOPT_KEY_NOSEND = (1 << 4),
+ TCP_AUTHOPT_KEY_NORECV = (1 << 5),
};

/**
* enum tcp_authopt_alg - Algorithms for TCP Authentication Option
*/
diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index 3d2c4283923f..331bf3e8b66a 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -363,10 +363,12 @@ static struct tcp_authopt_key_info *tcp_authopt_lookup_send(struct netns_tcp_aut
int l3index = -1;

hlist_for_each_entry_rcu(key, &net->head, node, 0) {
if (send_id >= 0 && key->send_id != send_id)
continue;
+ if (key->flags & TCP_AUTHOPT_KEY_NOSEND)
+ continue;
if (key->flags & TCP_AUTHOPT_KEY_ADDR_BIND)
if (!tcp_authopt_key_match_sk_addr(key, addr_sk))
continue;
if (key->flags & TCP_AUTHOPT_KEY_IFINDEX) {
if (l3index < 0)
@@ -612,11 +614,13 @@ int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt)

#define TCP_AUTHOPT_KEY_KNOWN_FLAGS ( \
TCP_AUTHOPT_KEY_DEL | \
TCP_AUTHOPT_KEY_EXCLUDE_OPTS | \
TCP_AUTHOPT_KEY_ADDR_BIND | \
- TCP_AUTHOPT_KEY_IFINDEX)
+ TCP_AUTHOPT_KEY_IFINDEX | \
+ TCP_AUTHOPT_KEY_NOSEND | \
+ TCP_AUTHOPT_KEY_NORECV)

int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
{
struct tcp_authopt_key opt;
struct tcp_authopt_info *info;
@@ -1524,10 +1528,13 @@ static struct tcp_authopt_key_info *tcp_authopt_lookup_recv(struct sock *sk,

if (l3index != key->l3index)
continue;
}
*anykey = true;
+ // If only keys with norecv flag are present still consider that
+ if (key->flags & TCP_AUTHOPT_KEY_NORECV)
+ continue;
if (recv_id >= 0 && key->recv_id != recv_id)
continue;
if (better_key_match(result, key))
result = key;
else if (result)
--
2.25.1