Re: [RFCv3 01/15] tcp: authopt: Initial support and key management

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Leonard,

On 8/24/21 10:34 PM, Leonard Crestez wrote:
[..]
> --- /dev/null
> +++ b/include/net/tcp_authopt.h
> @@ -0,0 +1,65 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +#ifndef _LINUX_TCP_AUTHOPT_H
> +#define _LINUX_TCP_AUTHOPT_H
> +
> +#include <uapi/linux/tcp.h>
> +
> +/**
> + * struct tcp_authopt_key_info - Representation of a Master Key Tuple as per RFC5925
> + *
> + * Key structure lifetime is only protected by RCU so readers needs to hold a
> + * single rcu_read_lock until they're done with the key.
> + */
> +struct tcp_authopt_key_info {
> +	struct hlist_node node;
> +	struct rcu_head rcu;
> +	/* Local identifier */
> +	u32 local_id;

It's unused now, can be removed.

[..]
> +
> +/**
> + * enum tcp_authopt_key_flag - flags for `tcp_authopt.flags`
> + *
> + * @TCP_AUTHOPT_KEY_DEL: Delete the key by local_id and ignore all other fields.
                                              ^
By send_id and recv_id.
Also, tcp_authopt_key_match_exact() seems to check
TCP_AUTHOPT_KEY_ADDR_BIND. I wounder if that makes sense to relax it in
case of TCP_AUTHOPT_KEY_DEL to match only send_id/recv_id if addr isn't
specified (no hard feelings about it, though).

[..]
> +#ifdef CONFIG_TCP_AUTHOPT
> +	case TCP_AUTHOPT: {
> +		struct tcp_authopt info;
> +
> +		if (get_user(len, optlen))
> +			return -EFAULT;
> +
> +		lock_sock(sk);
> +		tcp_get_authopt_val(sk, &info);
> +		release_sock(sk);
> +
> +		len = min_t(unsigned int, len, sizeof(info));
> +		if (put_user(len, optlen))
> +			return -EFAULT;
> +		if (copy_to_user(optval, &info, len))
> +			return -EFAULT;
> +		return 0;

Failed tcp_get_authopt_val() lookup in:
:       if (!info)
:               return -EINVAL;

will leak uninitialized kernel memory from stack.
ASLR guys defeated.

[..]
> +#define TCP_AUTHOPT_KNOWN_FLAGS ( \
> +	TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED)
> +
> +int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen)
> +{
> +	struct tcp_authopt opt;
> +	struct tcp_authopt_info *info;
> +
> +	sock_owned_by_me(sk);
> +
> +	/* If userspace optlen is too short fill the rest with zeros */
> +	if (optlen > sizeof(opt))
> +		return -EINVAL;

More like
:	if (unlikely(len > sizeof(opt))) {
:		err = check_zeroed_user(optval + sizeof(opt),
:					len - sizeof(opt));
:		if (err < 1)
:			return err == 0 ? -EINVAL : err;
:		len = sizeof(opt);
:		if (put_user(len, optlen))
:			return -EFAULT;
:	}

> +	memset(&opt, 0, sizeof(opt));
> +	if (copy_from_sockptr(&opt, optval, optlen))
> +		return -EFAULT;
> +
> +	if (opt.flags & ~TCP_AUTHOPT_KNOWN_FLAGS)
> +		return -EINVAL;
> +
> +	info = __tcp_authopt_info_get_or_create(sk);
> +	if (IS_ERR(info))
> +		return PTR_ERR(info);
> +
> +	info->flags = opt.flags & TCP_AUTHOPT_KNOWN_FLAGS;
> +
> +	return 0;
> +}

[..]
> +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;
> +
> +	sock_owned_by_me(sk);
> +
> +	/* If userspace optlen is too short fill the rest with zeros */
> +	if (optlen > sizeof(opt))
> +		return -EINVAL;

Ditto

> +	memset(&opt, 0, sizeof(opt));
> +	if (copy_from_sockptr(&opt, optval, optlen))
> +		return -EFAULT;
> +
> +	if (opt.flags & ~TCP_AUTHOPT_KEY_KNOWN_FLAGS)
> +		return -EINVAL;
> +
> +	if (opt.keylen > TCP_AUTHOPT_MAXKEYLEN)
> +		return -EINVAL;
> +
> +	/* Delete is a special case: */
> +	if (opt.flags & TCP_AUTHOPT_KEY_DEL) {
> +		info = rcu_dereference_check(tcp_sk(sk)->authopt_info, lockdep_sock_is_held(sk));
> +		if (!info)
> +			return -ENOENT;
> +		key_info = tcp_authopt_key_lookup_exact(sk, info, &opt);
> +		if (!key_info)
> +			return -ENOENT;
> +		tcp_authopt_key_del(sk, info, key_info);

Doesn't seem to be safe together with tcp_authopt_select_key().
A key can be in use at this moment - you have to add checks for it.

> +		return 0;
> +	}
> +
> +	/* check key family */
> +	if (opt.flags & TCP_AUTHOPT_KEY_ADDR_BIND) {
> +		if (sk->sk_family != opt.addr.ss_family)
> +			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);
> +
> +	/* If an old key exists with exact ID then remove and replace.
> +	 * RCU-protected readers might observe both and pick any.
> +	 */
> +	key_info = tcp_authopt_key_lookup_exact(sk, info, &opt);
> +	if (key_info)
> +		tcp_authopt_key_del(sk, info, key_info);
> +	key_info = sock_kmalloc(sk, sizeof(*key_info), GFP_KERNEL | __GFP_ZERO);
> +	if (!key_info)
> +		return -ENOMEM;

So, you may end up without any key.
Also, replacing a key is not at all safe: you may receive old segments
which you in turn will discard and reset the connection.

I think the limitation RFC puts on removing keys in use and replacing
existing keys are actually reasonable. Probably, it'd be better to
enforce "key in use => desired key is different (or key_outdated flag)
=> key not in use => key may be removed" life-cycle of MKT.

Thanks,
            Dmitry



[Index of Archives]     [Kernel]     [Gnu Classpath]     [Gnu Crypto]     [DM Crypt]     [Netfilter]     [Bugtraq]

  Powered by Linux