RCR: UNIX credentials

G

Guillaume Marcais

--=-LH2RX20AHG69TpQAZ+qL
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

To get a feel before submitting a real RCR:

Abstract:
Add calls to UNIXSocket to pass around the UNIX credentials (i.e. pid,
uid and gid).

Problem:
There is no way, that I know of, within Ruby to find out which process
is connected at the other end of a UNIX socket.
Ex:
[gus@comp unix_credentials]$ irb
irb(main):001:0> require 'socket'
=> true
irb(main):002:0> s = UNIXServer.new('unixtest')
=> #<UNIXServer:unixtest>
irb(main):003:0> t = s.accept
=> #<UNIXSocket:0x4396d330>
irb(main):004:0> t.peeraddr
=> ["AF_UNIX", ""] # Doesn't tell you anything!

Proposal:
I propose the addition of two methods, send_credentials and
recv_credentils to pass through a UNIX socket the process id, user id
and group id.

Analysis:
The Linux system provide a mechanism to send UNIX credentials as
ancilary data (the same way as you can pass file descriptors,
implemented in Ruby as send_io). Other UNIX implementation I believe
offer similar mechanism.
One difference with just sending this data directly through the UNIX
socket (for example as a Mashalled array containing the pid, uid and
gid) is that the system does some checks on the values send (unless you
are root and are allowed to lie).

Implementation:
The attached patch implements it. It has been tested on Linux 2.4 only.
It is heavily inspired by the implementation of send_io. Usage example:

server:
irb(main):001:0> require 'socket'
=> true
irb(main):002:0> s = UNIXServer.new('unixtest')
=> #<UNIXServer:unixtest>
irb(main):003:0> t = s.accept
=> #<UNIXSocket:0x4bf81330>
irb(main):004:0> t.recv_credentials
=> [1933, 501, 501]

client:
irb(main):001:0> require 'socket'
=> true
irb(main):002:0> s = UNIXSocket.new("unixtest")
=> #<UNIXSocket:unixtest>
irb(main):003:0> s.send_credentials
=> nil
irb(main):004:0> Process.pid
=> 1933

Guillaume.


--=-LH2RX20AHG69TpQAZ+qL
Content-Disposition: attachment; filename=unix-credentials.patch
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-diff; charset=ISO-8859-1

--- ruby/ext/socket/socket.c Mon Apr 5 03:45:22 2004
+++ ruby-1.8.1-unix-credentials/ext/socket/socket.c Tue May 18 20:10:14 200=
4
@@ -1656,6 +1656,163 @@
}
=20
static VALUE
+unix_send_credentials(argc, argv, sock)
+ int argc;
+ VALUE *argv, sock;
+{
+#if defined(HAVE_SENDMSG) && (defined(HAVE_ST_MSG_CONTROL) || defined(HAVE=
_ST_MSG_ACCRIGHTS)) && defined(SCM_CREDENTIALS)
+ OpenFile *fptr;
+ int pid, uid, gid;
+ struct msghdr msg;
+ struct iovec vec[1];
+ char buf[1];
+ int len;
+ struct ucred *creds;
+#if defined(HAVE_ST_MSG_CONTROL)
+ char ancillary[CMSG_SPACE(sizeof(struct ucred))];
+ struct cmsghdr *cmsg;
+#else
+ struct ucred creds_data;
+#endif
+ =20
+ GetOpenFile(sock, fptr);
+
+ rb_scan_args(argc, argv, "03", &pid, &uid, &gid);
+
+ /* Linux and Solaris doesn't work if msg_iov is NULL. */
+ buf[0] =3D '\0';
+ vec[0].iov_base =3D buf;
+ vec[0].iov_len =3D 1;
+ msg.msg_iov =3D vec;
+ msg.msg_iovlen =3D 1;
+ =20
+ msg.msg_name =3D NULL;
+ msg.msg_namelen =3D 0;
+#if defined(HAVE_ST_MSG_CONTROL) =20
+ msg.msg_control =3D ancillary;
+ msg.msg_controllen =3D sizeof(ancillary);
+ msg.msg_flags =3D 0;
+ cmsg =3D CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len =3D CMSG_LEN(sizeof(struct ucred));
+ cmsg->cmsg_level =3D SOL_SOCKET;
+ cmsg->cmsg_type =3D SCM_CREDENTIALS;
+ creds =3D (struct ucred *)CMSG_DATA(cmsg);
+ msg.msg_controllen =3D cmsg->cmsg_len;
+#else
+ creds =3D &creds_data;
+ msg.msg_accrights =3D (caddr_t)creds;
+ msg.msg_accrightslen =3D sizeof(struct ucred);
+#endif
+
+ if (FIXNUM_P(pid)) {
+ creds->pid =3D FIX2INT(pid);
+ } else if (NIL_P(pid)) {
+ creds->pid =3D getpid();
+ } else {
+ rb_raise(rb_eTypeError, "FixNum");
+ }
+ if (FIXNUM_P(uid)) {
+ creds->uid =3D FIX2INT(uid);
+ } else if (NIL_P(uid)) {
+ creds->uid =3D getuid();
+ } else {
+ rb_raise(rb_eTypeError, "FixNum");
+ }
+ if (FIXNUM_P(gid)) {
+ creds->gid =3D FIX2INT(gid);
+ } else if (NIL_P(gid)) {
+ creds->gid =3D getgid();
+ } else {
+ rb_raise(rb_eTypeError, "FixNum");
+ }
+ =20
+ if ((len =3D sendmsg(fileno(fptr->f), &msg, 0)) =3D=3D -1)
+ rb_sys_fail("sendmsg(2)");
+ =20
+ return Qnil;
+#else=20
+ rb_notimplement();
+ return Qnil; /* not reached */
+#endif
+}
+
+
+static VALUE
+unix_recv_credentials(sock)
+ VALUE sock;
+{
+#if defined(HAVE_RECVMSG) && (defined(HAVE_ST_MSG_CONTROL) || defined(HAVE=
_ST_MSG_ACCRIGHTS)) && defined(SCM_CREDENTIALS)
+ OpenFile *fptr;
+ struct msghdr msg;
+ struct iovec vec;
+ char data_buf[1], control_buf[1024];
+ int len, true;
+ struct ucred *creds;
+#if defined(HAVE_ST_MSG_CONTROL)
+ struct cmsghdr *cmsg;
+#endif
+ =20
+ GetOpenFile(sock, fptr);
+ =20
+ thread_read_select(fileno(fptr->f));
+
+ true =3D 1;
+ if(setsockopt(fileno(fptr->f), SOL_SOCKET, SO_PASSCRED,=20
+ &true, sizeof(int)) < 0) {
+ rb_raise(rb_eSocket, "UNIX credentials cannot be received");
+ }
+
+ msg.msg_name =3D NULL;
+ msg.msg_namelen =3D 0;
+ vec.iov_base =3D data_buf;
+ vec.iov_len =3D sizeof(data_buf);
+ msg.msg_iov =3D &vec;
+ msg.msg_iovlen =3D 1;
+ msg.msg_control =3D control_buf;
+ msg.msg_controllen =3D sizeof(control_buf);
+ msg.msg_flags =3D 0;
+ =20
+#if !defined(HAVE_ST_MSG_CONTROL)
+ msg.msg_accrights =3D (caddr_t)creds;
+ msg.msg_accrightslen =3D sizeof(struct ucred);
+#endif
+ =20
+ if ((len =3D recvmsg(fileno(fptr->f), &msg, 0)) =3D=3D -1)
+ rb_sys_fail("recvmsg(2)");
+ =20
+
+#if defined(HAVE_ST_MSG_CONTROL)
+ for(cmsg =3D CMSG_FIRSTHDR(&msg);
+ cmsg !=3D NULL;
+ cmsg =3D CMSG_NXTHDR(&msg, cmsg)) {
+ if(cmsg->cmsg_level =3D=3D SOL_SOCKET=20
+ && cmsg->cmsg_type =3D=3D SCM_CREDENTIALS) {
+ creds =3D (struct ucred *)CMSG_DATA(cmsg);
+ break;
+ }
+ }
+#endif
+ =20
+ if(
+#if defined(HAVE_ST_MSG_CONTROL)
+ cmsg =3D=3D NULL
+#else =20
+ msg.msg_accrightslen !=3D sizeof(struct ucred)
+#endif =20
+ ) {
+ rb_raise(rb_eSocket, "Credential were not passed");
+ }
+ =20
+ return rb_ary_new3(3, INT2FIX(creds->pid), INT2FIX(creds->uid),
+ INT2FIX(creds->gid));
+
+#else
+ rb_notimplement();
+ return Qnil; /* not reached */
+#endif
+}
+
+static VALUE
unix_accept(sock)
VALUE sock;
{
@@ -2468,6 +2625,9 @@
rb_define_method(rb_cUNIXSocket, "recvfrom", unix_recvfrom, -1);
rb_define_method(rb_cUNIXSocket, "send_io", unix_send_io, 1);
rb_define_method(rb_cUNIXSocket, "recv_io", unix_recv_io, -1);
+ rb_define_method(rb_cUNIXSocket, "send_credentials", unix_send_credent=
ials, -1);
+ rb_define_method(rb_cUNIXSocket, "recv_credentials", unix_recv_credent=
ials, 0);
+
rb_define_singleton_method(rb_cUNIXSocket, "socketpair", unix_s_socket=
pair, -1);
rb_define_singleton_method(rb_cUNIXSocket, "pair", unix_s_socketpair, =
-1);
=20

--=-LH2RX20AHG69TpQAZ+qL--
 
Y

Yukihiro Matsumoto

Hi,

In message "RCR: UNIX credentials"

|To get a feel before submitting a real RCR:
|
|Abstract:
|Add calls to UNIXSocket to pass around the UNIX credentials (i.e. pid,
|uid and gid).
|
|Problem:
|There is no way, that I know of, within Ruby to find out which process
|is connected at the other end of a UNIX socket.

I like the idea. But I don't want to merge it if it's Linux
specific. Any idea?

matz.
 
G

Guillaume Marcais

Le 18 mai 04, à 23:20, Yukihiro Matsumoto a écrit :
I like the idea. But I don't want to merge it if it's Linux
specific. Any idea?

Two ideas.

1) This works fine too:
class UNIXSocket
def getpeercredentials
getsockopt(Socket::SOL_SOCKET, Socket::SO_PEERCRED).unpack("i3")
end
end

No need for C code. Tested on Linux. A web search tells me that the
SO_PEERCRED option is supported on the BSD family as well. The closest
thing to a BSD machine I have is a MacOS X box, which doesn't support
it. Can anybody try this piece of code on *BSD?

2) There seems to be a similar mechanism as the Linux one to pass this
information as ancillary data and *BSD, with the SCM_CREDS cmsg type
and struct cmsgcred. But once again, Apple is failing on me: the
definition of struct cmsgcred is surrounded by #ifndef __APPLE__ ...
#endif.

The advantage of sending ancillary data compared to getsockopt is that
you can send you euid and egid instead of uid and gid.

Guillaume.
 
A

Andre Nathan

1) This works fine too:
class UNIXSocket
def getpeercredentials
getsockopt(Socket::SOL_SOCKET, Socket::SO_PEERCRED).unpack("i3")
end
end

No need for C code. Tested on Linux. A web search tells me that the
SO_PEERCRED option is supported on the BSD family as well.

According to David Wheeler[1] this is a Linux-only thing.

Also, the Unix Network Programming book says there's no portable way of
doing that (but shows how to do it on FreeBSD, using SCM_CREDS IIRC).

Best regards,
Andre


[1]http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/sockets.html
 
T

ts

G> it. Can anybody try this piece of code on *BSD?


obsd% ruby -vrsocket -e 'p Socket::SO_PEERCRED'
ruby 1.9.0 (2004-04-30) [sparc-openbsd3.5]
-e:1: uninitialized constant Socket::SO_PEERCRED (NameError)
obsd%


Guy Decoux
 
T

ts

t> obsd% ruby -vrsocket -e 'p Socket::SO_PEERCRED'
t> ruby 1.9.0 (2004-04-30) [sparc-openbsd3.5]

I've forgotten to say : look at src/backend/libpq/hba.c in the PostgreSQL
distribution (7.4.x)



Guy Decoux
 
T

Tanaka Akira

Guillaume Marcais said:
The Linux system provide a mechanism to send UNIX credentials as
ancilary data (the same way as you can pass file descriptors,
implemented in Ruby as send_io). Other UNIX implementation I believe
offer similar mechanism.

I surveyed the feature sometime ago.
Unfortunately it is not so portable.

1. SCM_CREDS and getpeerid.

There are 2 kind of interfaces.
SCM_CREDS: a client must send a credential explicitly.
getpeerid: a client doesn't need to send a credential.

2. getpeerid

As far as I know, following system has getpeerid or similar.

FreeBSD 4.6 (LOCAL_PEERCRED, http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libc/gen/getpeereid.3)
OpenBSD 3.0 (http://www.openbsd.org/cgi-bin/man.cgi?query=getpeereid&sektion=2)
Linux (socket(7): getsockopt SO_PEERCRED)
MacOS X (http://www.hmug.org/man/3/getpeereid.html)

DJB recommends getpeerid.
http://cr.yp.to/docs/secureipc.html

2. SCM_CREDS

As far as I know, following system has SCM_CREDS or similar.
However, credential information varies on each system.

FreeBSD 3.0 (recvmsg(2): SCM_CREDS: pid, uid, euid, gid, supp groups)
NetBSD 1.4.0 (unix(4): LOCAL_CREDS, SCM_CREDS: uid, euid, gid, egid, supp groups: http://www.netbsd.org/Changes/changes-1.4.html)
OpenBSD 2.5 (SCM_CREDS: uid, euid, gid, egid, supp groups)
Linux (unix(7): SCM_CREDENTIALS: pid, uid, gid)

3. send_io/recv_io may be usable for authentication.

See: Secure UNIX Programming FAQ
4.4) How do I authenticate a non-parent process?
http://www.whitefang.com/sup/secure-faq.html
 
G

Guillaume Marcais

So, should we implement a UNIXSocket#getpeerid method, using getpeerid()
or SO_PEERCRED, depending on what's available? The way of doing things
with getpeerid seems more natural than SCM_CREDS.

I'll resumit an RCR.

Guillaume.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top