Discussion:
A bug with getsockopt(SOL_LOCAL, LOCAL_PEERCRED) ?
Gleb Popov
2021-04-15 18:58:36 UTC
Permalink
Hello hackers.

I'm using getsockopt(SOL_LOCAL, LOCAL_PEERCRED) on a unix socket and for
some reason it fails for me. I came up with a minimal reproduction
testcase: https://arrowd.name/un.cpp

Steps to reproduce:
1. c++ un.cpp
2. ./a.out
3. nc -U foobar

This results in

getsockopt
failed with
Socket is not connected

Am I missing something?
Konstantin Belousov
2021-04-15 19:15:54 UTC
Permalink
Post by Gleb Popov
Hello hackers.
I'm using getsockopt(SOL_LOCAL, LOCAL_PEERCRED) on a unix socket and for
some reason it fails for me. I came up with a minimal reproduction
testcase: https://arrowd.name/un.cpp
1. c++ un.cpp
2. ./a.out
3. nc -U foobar
This results in
getsockopt
failed with
Socket is not connected
Am I missing something?
You are calling getsockopt(2) in the listen socket, not on the connected one.
Replace s with s2 in the call.
Gleb Popov
2021-04-15 19:21:39 UTC
Permalink
Post by Konstantin Belousov
You are calling getsockopt(2) in the listen socket, not on the connected one.
Replace s with s2 in the call.
The `man unix ` says:

The credentials presented to the server (the
listen(2) caller) are those of the client
when it called connect(2); the credentials
presented to the client (the connect(2)
caller) are those of the server when it
called listen(2).

This is what I actually want - to find out credentials of the connected
user.
Mark Millard via freebsd-hackers
2021-04-15 19:58:08 UTC
Permalink
On Thu, Apr 15, 2021 at 10:16 PM Konstantin Belousov <kostikbel at gmail.com>
Post by Konstantin Belousov
You are calling getsockopt(2) in the listen socket, not on the connected one.
Replace s with s2 in the call.
The credentials presented to the server (the
listen(2) caller) are those of the client
when it called connect(2); the credentials
presented to the client (the connect(2)
caller) are those of the server when it
called listen(2).
This is what I actually want - to find out credentials of the connected
user.
The way I read the above quote and your code and
related documentation: s2 after the accept4 assignment
is specific to the client's specific connect and
will give access to the connected user's credentials
--but s is not specific to the specific connect in
question (after that assignment or later) and would
not give the information that you indicate that you
want: you need a connection-specific identifier.

In other words, it looks to me like what you quoted
agrees with what Konstantin reported.

===
Mark Millard
marklmi at yahoo.com
( dsl-only.net went
away in early 2018-Mar)
Gleb Popov
2021-04-21 18:27:48 UTC
Permalink
Post by Mark Millard via freebsd-hackers
The way I read the above quote and your code and
related documentation: s2 after the accept4 assignment
is specific to the client's specific connect and
will give access to the connected user's credentials
--but s is not specific to the specific connect in
question (after that assignment or later) and would
not give the information that you indicate that you
want: you need a connection-specific identifier.
In other words, it looks to me like what you quoted
agrees with what Konstantin reported.
===
Mark Millard
marklmi at yahoo.com
( dsl-only.net went
away in early 2018-Mar)
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?
Mark Millard via freebsd-hackers
2021-04-21 22:00:21 UTC
Permalink
Post by Gleb Popov
Post by Mark Millard via freebsd-hackers
The way I read the above quote and your code and
related documentation: s2 after the accept4 assignment
is specific to the client's specific connect and
will give access to the connected user's credentials
--but s is not specific to the specific connect in
question (after that assignment or later) and would
not give the information that you indicate that you
want: you need a connection-specific identifier.
In other words, it looks to me like what you quoted
agrees with what Konstantin reported.
. . .
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.

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

===
Mark Millard
marklmi at yahoo.com
( dsl-only.net went
away in early 2018-Mar)
Gleb Popov
2021-04-22 04:54:26 UTC
Permalink
Post by Gleb Popov
Post by Gleb Popov
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
https://github.com/CollaboraOnline/online/blob/master/net/Socket.cpp#L805
Post by Gleb Popov
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,
getsockopt(getFD(), 0, LOCAL_PEERCRED, &creds, &credSize)
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.
Post by Gleb Popov
See also the following that involved replacing a SOL_LOCAL
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.
Mark Millard via freebsd-hackers
2021-04-22 08:56:28 UTC
Permalink
Post by Gleb Popov
Post by Mark Millard via freebsd-hackers
Post by Gleb Popov
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,
getsockopt(getFD(), 0, LOCAL_PEERCRED, &creds, &credSize)
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.
Post by Mark Millard via freebsd-hackers
See also the following that involved replacing a SOL_LOCAL
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.
(Is there any Linux documentation indicating that Linux
is required to allow that? POSIX? Etc,? Or is such code
depending on such properties operating outside the range
of the guarantees?)

Is the context linux compat code? Direct FreeBSD code?

It would be FreeBSD's compat handling that needs to match
Linux handling if FreeBSD is to span compatibility in the
subject area.

Does the compat code work as Linux (implicitly?) specifies?
(If not it might be more likely that FreeBSD would change
things sufficiently for it to work in at least that kind
of context.)

But if the compat code already matches the Linux behavior
for which socket(s) allow the accessbut direct FreeBSD does not . . .

FreeBSD appears to have its own programming model for direct
use, not exposing the temporary copy of the peercred that
is associated with the listening socket. If the compat code
works for Linux, it is not so obvious that FreeBSD would
change anything since it appears to have a working, usable
API: direct FreeBSD code needs to use FreeBSD's API. I'm
not sure how much FreeBSD tries to make direct FreeBSD code
allow code designed for Linux to work, except to help with
the Linux compat code doing the right thing in a simpler
way than otherwise.



===
Mark Millard
marklmi at yahoo.com
( dsl-only.net went
away in early 2018-Mar)
Konstantin Belousov
2021-04-22 10:33:33 UTC
Permalink
Post by Gleb Popov
Post by Gleb Popov
Post by Gleb Popov
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
https://github.com/CollaboraOnline/online/blob/master/net/Socket.cpp#L805
Post by Gleb Popov
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,
getsockopt(getFD(), 0, LOCAL_PEERCRED, &creds, &credSize)
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.
Post by Gleb Popov
See also the following that involved replacing a SOL_LOCAL
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.

Show minimal code that works for you on Linux.
Mark Millard via freebsd-hackers
2021-04-22 16:11:23 UTC
Permalink
Post by Konstantin Belousov
Post by Gleb Popov
Post by Gleb Popov
Post by Gleb Popov
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
https://github.com/CollaboraOnline/online/blob/master/net/Socket.cpp#L805
Post by Gleb Popov
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,
getsockopt(getFD(), 0, LOCAL_PEERCRED, &creds, &credSize)
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.
Post by Gleb Popov
See also the following that involved replacing a SOL_LOCAL
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.
Post by Konstantin Belousov
Show minimal code that works for you on Linux.
===
Mark Millard
marklmi at yahoo.com
( dsl-only.net went
away in early 2018-Mar)
Gleb Popov
2021-04-22 18:49:15 UTC
Permalink
Post by Konstantin Belousov
There is no peer for listening socket.
Show minimal code that works for you on Linux.
Here you go: http://arrowd.name/un_linux.cpp
It is almost identical to FreeBSD one, but works for me on ArchLinux.
Konstantin Belousov
2021-04-22 19:34:38 UTC
Permalink
Post by Gleb Popov
Post by Konstantin Belousov
There is no peer for listening socket.
Show minimal code that works for you on Linux.
Here you go: http://arrowd.name/un_linux.cpp
It is almost identical to FreeBSD one, but works for me on ArchLinux.
Of course it works because it uses s2, that is, connected and not listening
socket, for getsockopt(), same as the working FreeBSD version.
Mark Millard via freebsd-hackers
2021-04-22 20:33:39 UTC
Permalink
Post by Konstantin Belousov
Post by Gleb Popov
Post by Konstantin Belousov
There is no peer for listening socket.
Show minimal code that works for you on Linux.
Here you go: http://arrowd.name/un_linux.cpp
It is almost identical to FreeBSD one, but works for me on ArchLinux.
Of course it works because it uses s2, that is, connected and not listening
socket, for getsockopt(), same as the working FreeBSD version.
I made a variant that tries s2 and then s, printing the
peercred uid, group id, and pid fields, and built it on
Fedora 34. The result was:

# ./a.out & ncat -U foobar
[1] 18646
s2 data is uid field: 0 groups[0] field: 0 pid field: 18647
s data is uid field: 0 groups[0] field: 0 pid field: 18646
[1]+ Done ./a.out


On FreeBSD:

# ./a.out & nc -U foobar
s2 data is uid field: 0 groups[0] field: 0 pid field: 6984
getsockopt for s
failed with
Socket is not connected
[1] Done ./a.out

The code is:

#ifndef __FreeBSD__
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

#include <sys/types.h>

#ifdef __FreeBSD__
#define FOR_PEERCRED 0
#define CR_GROUP_ID cr_groups[0]
#else // Linux
#define xucred ucred
#define FOR_PEERCRED SOL_SOCKET
#define LOCAL_PEERCRED SO_PEERCRED
#define cr_uid uid
#define CR_GROUP_ID gid
#define cr_pid pid
#endif

#include <sys/socket.h>
#include <sys/un.h>
#ifdef __FreeBSD__
#include <sys/ucred.h>
#endif
#include <netinet/in.h>
#include <netinet/tcp.h>


void failure(char const * err)
{
puts(err);
puts("failed with");
puts(strerror(errno));
}

void die(char const * err)
{
failure(err);
unlink("foobar");
exit(1);
}

int main()
{
int s = socket(PF_LOCAL, SOCK_STREAM, 0);
if(s < 0) die("socket");

struct sockaddr_un addrunix;
memset(&addrunix, 0, sizeof(addrunix));
addrunix.sun_family = AF_UNIX;
memcpy(addrunix.sun_path, "foobar", 6);

int r = bind(s, (const struct sockaddr *)&addrunix, sizeof(struct sockaddr_un));
if(r < 0) die("bind");

listen(s, 64);

int s2;
do
{
s2 = accept4(s, 0, 0, 0);
} while (s2 < 0 && errno == EAGAIN);
if(s2 < 0) die("accept4");

struct xucred s2_creds;
socklen_t s2_credSize = sizeof(struct xucred);
r = getsockopt(s2, FOR_PEERCRED, LOCAL_PEERCRED, &s2_creds, &s2_credSize);
if(r < 0) failure("getsockopt for s2");
else
printf("s2 data is uid field: %jd groups[0] field: %jd pid field: %jd\n",
(uintmax_t) s2_creds.cr_uid, (uintmax_t) s2_creds.CR_GROUP_ID, (uintmax_t) s2_creds.cr_pid);

struct xucred s_creds;
socklen_t s_credSize = sizeof(struct xucred);
r = getsockopt(s, FOR_PEERCRED, LOCAL_PEERCRED, &s_creds, &s_credSize);
if(r < 0) failure("getsockopt for s");
else
printf("s data is uid field: %jd groups[0] field: %jd pid field: %jd\n",
(uintmax_t) s_creds.cr_uid, (uintmax_t) s_creds.CR_GROUP_ID, (uintmax_t) s_creds.cr_pid);

unlink("foobar");
return 0;
}



===
Mark Millard
marklmi at yahoo.com
( dsl-only.net went
away in early 2018-Mar)
Mark Millard via freebsd-hackers
2021-04-22 20:47:51 UTC
Permalink
Post by Mark Millard via freebsd-hackers
Post by Konstantin Belousov
Post by Gleb Popov
Post by Konstantin Belousov
There is no peer for listening socket.
Show minimal code that works for you on Linux.
Here you go: http://arrowd.name/un_linux.cpp
It is almost identical to FreeBSD one, but works for me on ArchLinux.
Of course it works because it uses s2, that is, connected and not listening
socket, for getsockopt(), same as the working FreeBSD version.
I made a variant that tries s2 and then s, printing the
peercred uid, group id, and pid fields, and built it on
# ./a.out & ncat -U foobar
[1] 18646
s2 data is uid field: 0 groups[0] field: 0 pid field: 18647
s data is uid field: 0 groups[0] field: 0 pid field: 18646
[1]+ Done ./a.out
As a cross check, I also tried building and running on
ubuntu 2021.04 (so debian basead, vs. red hat based before):

# ./a.out & nc -U foobar
[1] 39348
s2 data is uid field: 0 groups[0] field: 0 pid field: 39349
s data is uid field: 0 groups[0] field: 0 pid field: 39348
[1]+ Done ./a.out

So two major branches of Linux based systems seem to
agree for the issue.

I do not have any other variants around to quickly check.
The tests were all on aarch64, by the way.
Post by Mark Millard via freebsd-hackers
# ./a.out & nc -U foobar
s2 data is uid field: 0 groups[0] field: 0 pid field: 6984
getsockopt for s
failed with
Socket is not connected
[1] Done ./a.out
#ifndef __FreeBSD__
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#ifdef __FreeBSD__
#define FOR_PEERCRED 0
#define CR_GROUP_ID cr_groups[0]
#else // Linux
#define xucred ucred
#define FOR_PEERCRED SOL_SOCKET
#define LOCAL_PEERCRED SO_PEERCRED
#define cr_uid uid
#define CR_GROUP_ID gid
#define cr_pid pid
#endif
#include <sys/socket.h>
#include <sys/un.h>
#ifdef __FreeBSD__
#include <sys/ucred.h>
#endif
#include <netinet/in.h>
#include <netinet/tcp.h>
void failure(char const * err)
{
puts(err);
puts("failed with");
puts(strerror(errno));
}
void die(char const * err)
{
failure(err);
unlink("foobar");
exit(1);
}
int main()
{
int s = socket(PF_LOCAL, SOCK_STREAM, 0);
if(s < 0) die("socket");
struct sockaddr_un addrunix;
memset(&addrunix, 0, sizeof(addrunix));
addrunix.sun_family = AF_UNIX;
memcpy(addrunix.sun_path, "foobar", 6);
int r = bind(s, (const struct sockaddr *)&addrunix, sizeof(struct sockaddr_un));
if(r < 0) die("bind");
listen(s, 64);
int s2;
do
{
s2 = accept4(s, 0, 0, 0);
} while (s2 < 0 && errno == EAGAIN);
if(s2 < 0) die("accept4");
struct xucred s2_creds;
socklen_t s2_credSize = sizeof(struct xucred);
r = getsockopt(s2, FOR_PEERCRED, LOCAL_PEERCRED, &s2_creds, &s2_credSize);
if(r < 0) failure("getsockopt for s2");
else
printf("s2 data is uid field: %jd groups[0] field: %jd pid field: %jd\n",
(uintmax_t) s2_creds.cr_uid, (uintmax_t) s2_creds.CR_GROUP_ID, (uintmax_t) s2_creds.cr_pid);
struct xucred s_creds;
socklen_t s_credSize = sizeof(struct xucred);
r = getsockopt(s, FOR_PEERCRED, LOCAL_PEERCRED, &s_creds, &s_credSize);
if(r < 0) failure("getsockopt for s");
else
printf("s data is uid field: %jd groups[0] field: %jd pid field: %jd\n",
(uintmax_t) s_creds.cr_uid, (uintmax_t) s_creds.CR_GROUP_ID, (uintmax_t) s_creds.cr_pid);
unlink("foobar");
return 0;
}
===
Mark Millard
marklmi at yahoo.com
( dsl-only.net went
away in early 2018-Mar)
Mark Millard via freebsd-hackers
2021-04-22 20:59:29 UTC
Permalink
[This is not about what Konstantin was asking about, but
about other details. Avoiding the irrelevant material
in the TO/CC.]
Post by Mark Millard via freebsd-hackers
Post by Mark Millard via freebsd-hackers
Post by Konstantin Belousov
Post by Gleb Popov
Post by Konstantin Belousov
There is no peer for listening socket.
Show minimal code that works for you on Linux.
Here you go: http://arrowd.name/un_linux.cpp
It is almost identical to FreeBSD one, but works for me on ArchLinux.
Of course it works because it uses s2, that is, connected and not listening
socket, for getsockopt(), same as the working FreeBSD version.
I made a variant that tries s2 and then s, printing the
peercred uid, group id, and pid fields, and built it on
# ./a.out & ncat -U foobar
[1] 18646
s2 data is uid field: 0 groups[0] field: 0 pid field: 18647
s data is uid field: 0 groups[0] field: 0 pid field: 18646
[1]+ Done ./a.out
So s2 gets the pid of the ncat and s gets the pid of a.out.

Is that what you expected?
Post by Mark Millard via freebsd-hackers
As a cross check, I also tried building and running on
# ./a.out & nc -U foobar
[1] 39348
s2 data is uid field: 0 groups[0] field: 0 pid field: 39349
s data is uid field: 0 groups[0] field: 0 pid field: 39348
[1]+ Done ./a.out
So s2 gets the pid of the nc and s gets the pid of a.out.

Same question applies.
Post by Mark Millard via freebsd-hackers
So two major branches of Linux based systems seem to
agree for the issue.
I do not have any other variants around to quickly check.
The tests were all on aarch64, by the way.
Post by Mark Millard via freebsd-hackers
# ./a.out & nc -U foobar
s2 data is uid field: 0 groups[0] field: 0 pid field: 6984
getsockopt for s
failed with
Socket is not connected
[1] Done ./a.out
Were you expecting s2 to get the pid of nc and
s to get the pid of a.out under FreeBSD as well?
Would that be getting what you want?
Post by Mark Millard via freebsd-hackers
Post by Mark Millard via freebsd-hackers
#ifndef __FreeBSD__
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#ifdef __FreeBSD__
#define FOR_PEERCRED 0
#define CR_GROUP_ID cr_groups[0]
#else // Linux
#define xucred ucred
#define FOR_PEERCRED SOL_SOCKET
#define LOCAL_PEERCRED SO_PEERCRED
#define cr_uid uid
#define CR_GROUP_ID gid
#define cr_pid pid
#endif
#include <sys/socket.h>
#include <sys/un.h>
#ifdef __FreeBSD__
#include <sys/ucred.h>
#endif
#include <netinet/in.h>
#include <netinet/tcp.h>
void failure(char const * err)
{
puts(err);
puts("failed with");
puts(strerror(errno));
}
void die(char const * err)
{
failure(err);
unlink("foobar");
exit(1);
}
int main()
{
int s = socket(PF_LOCAL, SOCK_STREAM, 0);
if(s < 0) die("socket");
struct sockaddr_un addrunix;
memset(&addrunix, 0, sizeof(addrunix));
addrunix.sun_family = AF_UNIX;
memcpy(addrunix.sun_path, "foobar", 6);
int r = bind(s, (const struct sockaddr *)&addrunix, sizeof(struct sockaddr_un));
if(r < 0) die("bind");
listen(s, 64);
int s2;
do
{
s2 = accept4(s, 0, 0, 0);
} while (s2 < 0 && errno == EAGAIN);
if(s2 < 0) die("accept4");
struct xucred s2_creds;
socklen_t s2_credSize = sizeof(struct xucred);
r = getsockopt(s2, FOR_PEERCRED, LOCAL_PEERCRED, &s2_creds, &s2_credSize);
if(r < 0) failure("getsockopt for s2");
else
printf("s2 data is uid field: %jd groups[0] field: %jd pid field: %jd\n",
(uintmax_t) s2_creds.cr_uid, (uintmax_t) s2_creds.CR_GROUP_ID, (uintmax_t) s2_creds.cr_pid);
struct xucred s_creds;
socklen_t s_credSize = sizeof(struct xucred);
r = getsockopt(s, FOR_PEERCRED, LOCAL_PEERCRED, &s_creds, &s_credSize);
if(r < 0) failure("getsockopt for s");
else
printf("s data is uid field: %jd groups[0] field: %jd pid field: %jd\n",
(uintmax_t) s_creds.cr_uid, (uintmax_t) s_creds.CR_GROUP_ID, (uintmax_t) s_creds.cr_pid);
unlink("foobar");
return 0;
}
===
Mark Millard
marklmi at yahoo.com
( dsl-only.net went
away in early 2018-Mar)
Gleb Popov
2021-04-23 08:46:33 UTC
Permalink
Post by Mark Millard via freebsd-hackers
So s2 gets the pid of the ncat and s gets the pid of a.out.
Is that what you expected?
<...>
So s2 gets the pid of the nc and s gets the pid of a.out.
Same question applies.
<...>
Post by Mark Millard via freebsd-hackers
Were you expecting s2 to get the pid of nc and
s to get the pid of a.out under FreeBSD as well?
Would that be getting what you want?
===
Mark Millard
marklmi at yahoo.com
( dsl-only.net went
away in early 2018-Mar)
Most probably, yes. I'm not 100% as I'm still trying to reach authors of
https://github.com/CollaboraOnline/online/blob/master/net/Socket.cpp#L811
and ask them what this code does.
On one hand, [1] hints that creds for the listening socket should indeed
match the user that started the program.
On the other hand, log message at [2] looks like they think that creds
contain credentials of the connected user. Like, the authors also mistaken
the listening socket with an accepted one.
I'm confused by this and keep asking on Collabora IRC and forums, but
didn't get an answer yet.

[1]:
https://github.com/CollaboraOnline/online/blob/master/net/Socket.cpp#L833
[2]:
https://github.com/CollaboraOnline/online/blob/master/net/Socket.cpp#L844
Gleb Popov
2021-04-23 09:59:52 UTC
Permalink
All right, I got an answer from Collabora and they confirmed that it is a
bug on their side.

They indeed wanted to check creds on the accepted socket, not the listening
one.

So, unless I am missing something, I think it is good to have FreeBSD error
out in this case.
Mark Millard via freebsd-hackers
2021-04-24 02:12:13 UTC
Permalink
Post by Gleb Popov
All right, I got an answer from Collabora and they confirmed that it is a
bug on their side.
They indeed wanted to check creds on the accepted socket, not the listening
one.
So, unless I am missing something, I think it is good to have FreeBSD error
out in this case.
FreeBSD's Linux-compatibility code may well be desired to
have the same behavior as Linux implementations do, even
if the specific example from Collabora via this issue was
found only mistakenly put the functionality to use (in
both Linux and FreeBSD). There may be other Linux code
that does deliberately put the functionality to use.

The FreeSD-native API properties need not be driven by
Linux implementation properites.

Konstantin has the examples to use in making the choices,
with pid information to help make the behavior clear.

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

Gleb Popov
2021-04-23 08:31:17 UTC
Permalink
Post by Konstantin Belousov
Post by Gleb Popov
Post by Konstantin Belousov
There is no peer for listening socket.
Show minimal code that works for you on Linux.
Here you go: http://arrowd.name/un_linux.cpp
It is almost identical to FreeBSD one, but works for me on ArchLinux.
Of course it works because it uses s2, that is, connected and not listening
socket, for getsockopt(), same as the working FreeBSD version.
Ugh, uploaded wrong version, sorry. I changed it back to "s" and it still
works.
Konstantin Belousov
2021-04-15 20:00:50 UTC
Permalink
Post by Gleb Popov
Post by Konstantin Belousov
You are calling getsockopt(2) in the listen socket, not on the connected one.
Replace s with s2 in the call.
The credentials presented to the server (the
listen(2) caller) are those of the client
when it called connect(2); the credentials
presented to the client (the connect(2)
caller) are those of the server when it
called listen(2).
This is what I actually want - to find out credentials of the connected
user.
Yes, so what? How you citation changes anything?
Loading...