A bug with getsockopt(SOL_LOCAL, LOCAL_PEERCRED) ?

Mark Millard marklmi at yahoo.com
Thu Apr 22 16:11:30 UTC 2021


On 2021-Apr-22, at 03:33, Konstantin Belousov <kostikbel at gmail.com> wrote:
> 
> On Thu, Apr 22, 2021 at 07:54:26AM +0300, Gleb Popov wrote:
>> On Thu, Apr 22, 2021 at 1:00 AM Mark Millard <marklmi at yahoo.com> wrote:
>> 
>>> 
>>> On 2021-Apr-21, at 11:27, Gleb Popov <arrowd at freebsd.org> wrote:
>>>> 
>>>> This makes sense, thanks.
>>>> 
>>>> However, this code works on Linux and seems to return credentials of the
>>> user that started the process. I actually stumbled upon this when porting
>>> this code:
>>> https://github.com/CollaboraOnline/online/blob/master/net/Socket.cpp#L805
>>>> 
>>>> Would it make sense if FreeBSD followed Linux semantics in this case? If
>>> not, what are my options for porting the software?
>>> 
>>> From what I can tell . . .
>>> 
>>> FreeBSD defines LOCAL_PEERCRED and what goes with its use, not linux.
>>> Linux defines SO_PEERCRED and what goes with its use, not FreeBSD.
>>> 
>>> If I understand right, your code is incompatible with the referenced
>>> CollaboraOnline  code from just after the #else (so __FreeBSD__ case,
>>> not the linux case):
>>> 
>>> getsockopt(getFD(), 0, LOCAL_PEERCRED, &creds, &credSize)
>>> vs. your:
>>> getsockopt(s, SOL_LOCAL, LOCAL_PEERCRED, &creds, &credSize)
>>> 
>>> Note the 0 vs. the SOL_LOCAL. Your code is a mix of Linux
>>> and FreeBSD code when it should not be.
>>> 
>> 
>> SOL_LOCAL is defined to 0, so this is fine.
>> 
>> 
>>> See also the following that involved replacing a SOL_LOCAL
>>> with a 0 for getsockopt used with LOCAL_PEERCRED:
>>> 
>>> https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234722
>>> 
>>> 
>> Yes, I'm aware that Linux SO_PEERCRED operates on socket level, while ours
>> operates on level 0. This is taken in account
>> in the code I posted.
>> 
>> As I said, the error stems from the fact that Linux allows getting creds
>> from the listening socket.
> 
> There is no peer for listening socket.

Well, I ran into the below while looking around as far as what
getsockopt gets access to for peercred on a listening socket:
net/unix/af_unix.c has unix_listen that it uses and that code
does "set credentials so connect can copy them" but the
getsockopt code has access to the copy that listen
established for making the copy. I initially show the
init_peercred(sk) side of things below.

static int unix_listen(struct socket *sock, int backlog)
{
	int err;
	struct sock *sk = sock->sk;
	struct unix_sock *u = unix_sk(sk);

	err = -EOPNOTSUPP;
	if (sock->type != SOCK_STREAM && sock->type != SOCK_SEQPACKET)
		goto out;	/* Only stream/seqpacket sockets accept */
	err = -EINVAL;
	if (!u->addr)
		goto out;	/* No listens on an unbound socket */
	unix_state_lock(sk);
	if (sk->sk_state != TCP_CLOSE && sk->sk_state != TCP_LISTEN)
		goto out_unlock;
	if (backlog > sk->sk_max_ack_backlog)
		wake_up_interruptible_all(&u->peer_wait);
	sk->sk_max_ack_backlog	= backlog;
	sk->sk_state		= TCP_LISTEN;
	/* set credentials so connect can copy them */
	init_peercred(sk);
	err = 0;

out_unlock:
	unix_state_unlock(sk);
out:
	return err;
}

where:

static void init_peercred(struct sock *sk)
{
	put_pid(sk->sk_peer_pid);
	if (sk->sk_peer_cred)
		put_cred(sk->sk_peer_cred);
	sk->sk_peer_pid  = get_pid(task_tgid(current));
	sk->sk_peer_cred = get_current_cred();
}

and unix_listen is used via:

static const struct proto_ops unix_stream_ops = {
	.family =	PF_UNIX,
. . .
	.listen =	unix_listen,
. . .

static const struct proto_ops unix_seqpacket_ops = {
	.family =	PF_UNIX,
. . .
	.listen =	unix_listen,
. . .

On the other side is the only use of SO_PEERCRED:

int sock_getsockopt(struct socket *sock, int level, int optname,
		    char __user *optval, int __user *optlen)
{
	struct sock *sk = sock->sk;
. . .
	case SO_PEERCRED:
	{
		struct ucred peercred;
		if (len > sizeof(peercred))
			len = sizeof(peercred);
		cred_to_ucred(sk->sk_peer_pid, sk->sk_peer_cred, &peercred);
		if (copy_to_user(optval, &peercred, len))
			return -EFAULT;
		goto lenout;
	}
. . .

used via (only place):

	if (level == SOL_SOCKET)
		err = sock_getsockopt(sock, level, optname, optval, optlen);
	else if (unlikely(!sock->ops->getsockopt))
		err = -EOPNOTSUPP;
	else
		err = sock->ops->getsockopt(sock, level, optname, optval,
					    optlen);

This code appears to return the copied peercred
information for SOL_SOCKET and SO_PEERCRED used
together.

I did not find any documentation that sk->sk_peer_cred
recorded by listen should be externally accessible via
getsockopt on the listen socket but it is from what I
can tell.

I'm only noting that having such a request seems to be
valid in the Linux implementation and is not rejected,
I'm not claiming details of which "peer" is involved in
the returned information or the like.

> Show minimal code that works for you on Linux.
> 

===
Mark Millard
marklmi at yahoo.com
( dsl-only.net went
away in early 2018-Mar)



More information about the freebsd-hackers mailing list