Project

General

Profile

Actions

Feature #19179

open

Support parsing SCM_CRED(ENTIALS) messages from ancillary messages

Added by kjtsanaktsidis (KJ Tsanaktsidis) over 1 year ago. Updated about 1 year ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:111194]

Description

Background

Linux and FreeBSD support processes at either end of a unix socket identifying themselves to the other party by passing an ancillary message of type SCM_CREDENTIALS (Linux) or SCM_CREDS (FreeBSD). The socket library contains code to parse these ancillary messages, but the only way this is exposed into Ruby code is by the Socket::AncillaryData#inspect method - e.g.

# On Linux
irb(main):002:0> s1, s2 = UNIXSocket.pair
=> [#<UNIXSocket:fd 5>, #<UNIXSocket:fd 6>]
irb(main):004:0> s2.setsockopt Socket::SOL_SOCKET, Socket::SO_PASSCRED, 1
=> 0
# struct ucred on Linux is (32-bit signed) pid_t, followed by (32-bit unsigned) uid_t, followed by
# (32-bit unsigned) gid_t
irb(main):008:0> ancdata = [Process.pid, Process.uid, Process.gid].pack("lLL")
=> "\x1ET\x05\x00\xE8\x03\x00\x00\xE8\x03\x00\x00"
# Socket::AncillaryData knows how to unmarshal the data into struct ucred
irb(main):010:0> ancmsg = Socket::AncillaryData.new(Socket::AF_UNIX, Socket::SOL_SOCKET, Socket::SCM_CRE
DENTIALS, ancdata)
=> #<Socket::AncillaryData: UNIX SOCKET CREDENTIALS pid=349214 uid=1000 gid=1000 (ucred)>
irb(main):011:0> s1.sendmsg "hi", 0, nil, ancmsg
=> 2
# ancillary message can be passed through
irb(main):012:0> _, _, _, recvanc = s2.recvmsg; recvanc
=> #<Socket::AncillaryData: UNIX SOCKET CREDENTIALS pid=349214 uid=1000 gid=1000 (ucred)>

On Linux, at least, a suitably privileged process can send any value through for the pid, uid, or gid, but the kernel will reject attempts by unprivileged processes to forge credentials in this way. So SCM_CREDENTIALS messages can be useful for certain systems programming tasks.

A somewhat wider array of operating systems support querying the identity of the other side of a socket using a socket option, variously SO_PEERCRED (Linux, OpenBSD) or LOCAL_PEERCRED (FreeBSD, MacOS). Again, the socket library is able to unmarshal the socket data into the correct structure on these various systems, but it's only exposed to Ruby code via #inspect - e.g.

irb(main):002:0> s1, s2 = UNIXSocket.pair
=> [#<UNIXSocket:fd 5>, #<UNIXSocket:fd 6>]
irb(main):014:0> s1.getsockopt Socket::SOL_SOCKET, Socket::SO_PEERCRED
=> #<Socket::Option: UNIX SOCKET PEERCRED pid=349214 euid=1000 egid=1000 (ucred)>

Ruby does however support e.g. BasicSocket#getpeereid, which could use SO_PEERCRED etc under the hood - so getting the uid/gid data is not totally impossible. I believe getting the pid is though.

irb(main):016:0> s1.getpeereid
=> [1000, 1000]

My proposal

I believe we should implement the following:

  • Socket::Credentials - this would be a struct which can contain all the various platform-specific pieces of credential info that can be transferred over a socket, such as uid, gid, pid, euid, egid, and group list.
  • Socket::AncillaryData#credentials - this would parse an SCM_CREDS or SCM_CREDENTIALS ancillary data message into the appropriate platform-specific struct, and return a Socket::Credentials instance containing that data. This would be analogous to Socket::AncillaryData#int; a method for interpreting the ancillary data in a certain form.
  • Socket::Option#credentials - This would parse a SO_PEERCRED or LOCAL_PEERCRED socket option response into the appropriate platform-specific struct, and return a Socket::Credentials instance containing that data. Again, this would be analogous to Socket::Option#int.

The existing struct ucred/struct xucred/struct sockpeercred/struct cmsgcred parsing code (used only for #inspect output) would be moved into Socket::Credentials, and Socket::AncillaryData#inspect/Socket::Option#inspect would be implemented in terms of Socket::Credentials.

This would nicely wrap a lot of parsing work that Ruby is already doing, into an API which allows Ruby code to take advantage of it.

Use-cases

My motivation for designing this feature came about whilst I was experimenting with some ideas for Ruby profilers. I wanted to allow a CLI tool to ask a Ruby process to start profiling itself by sending a message on a unix socket. Alongside the message, it would send a file descriptor which was the result of calling perf_event_open(2) in the CLI tool. In order to call perf_event_open(2), the CLI tool would need to be privileged. I also wanted the Ruby process to authenticate the request and make sure it came from the same UID that it was running as. Calling BasicSocket#getpeereuid would reveal the remote process to be running as UID 0, (or perhaps even some other UID, with sufficient ambient capabilities to call perf_event_open). Instead, I decided to make the CLI tool send a SCM_CREDENTIALS message containing the uid of the process to be profiled; that way, the kernel does all the policy checking on whether or not this is actually allowed, and the Ruby process receiving the message just needs to check if uid == Process.getuid.

I think, on Linux at least, that this feature will be useful for any kind of communication/authentication scheme between privileged & unprivileged processes over unix sockets.

My implementation

I have an implementation of roughly this in this pull request: https://github.com/ruby/ruby/pull/6822

Thanks!

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0