ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
* [ruby-core:111194] [Ruby master Feature#19179] Support parsing SCM_CRED(ENTIALS) messages from ancillary messages
@ 2022-12-04  1:59 kjtsanaktsidis (KJ Tsanaktsidis)
  2023-01-25  7:12 ` [ruby-core:112023] " kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: kjtsanaktsidis (KJ Tsanaktsidis) @ 2022-12-04  1:59 UTC (permalink / raw)
  To: ruby-core

Issue #19179 has been reported by kjtsanaktsidis (KJ Tsanaktsidis).

----------------------------------------
Feature #19179: Support parsing SCM_CRED(ENTIALS) messages from ancillary messages
https://bugs.ruby-lang.org/issues/19179

* Author: kjtsanaktsidis (KJ Tsanaktsidis)
* Status: Open
* Priority: Normal
----------------------------------------
## 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 th
 e 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!



-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [ruby-core:112023] [Ruby master Feature#19179] Support parsing SCM_CRED(ENTIALS) messages from ancillary messages
  2022-12-04  1:59 [ruby-core:111194] [Ruby master Feature#19179] Support parsing SCM_CRED(ENTIALS) messages from ancillary messages kjtsanaktsidis (KJ Tsanaktsidis)
@ 2023-01-25  7:12 ` kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core
  2023-02-06  8:10 ` [ruby-core:112230] " akr (Akira Tanaka) via ruby-core
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core @ 2023-01-25  7:12 UTC (permalink / raw)
  To: ruby-core; +Cc: kjtsanaktsidis (KJ Tsanaktsidis)

Issue #19179 has been updated by kjtsanaktsidis (KJ Tsanaktsidis).


Wondering if someone could please have a look? @akr someone suggested you would be the right person? 🙏

----------------------------------------
Feature #19179: Support parsing SCM_CRED(ENTIALS) messages from ancillary messages
https://bugs.ruby-lang.org/issues/19179#change-101455

* Author: kjtsanaktsidis (KJ Tsanaktsidis)
* Status: Open
* Priority: Normal
----------------------------------------
## 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!



-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [ruby-core:112230] [Ruby master Feature#19179] Support parsing SCM_CRED(ENTIALS) messages from ancillary messages
  2022-12-04  1:59 [ruby-core:111194] [Ruby master Feature#19179] Support parsing SCM_CRED(ENTIALS) messages from ancillary messages kjtsanaktsidis (KJ Tsanaktsidis)
  2023-01-25  7:12 ` [ruby-core:112023] " kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core
@ 2023-02-06  8:10 ` akr (Akira Tanaka) via ruby-core
  2023-02-09 10:55 ` [ruby-core:112300] " nobu (Nobuyoshi Nakada) via ruby-core
  2023-02-13  0:44 ` [ruby-core:112389] " kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core
  3 siblings, 0 replies; 5+ messages in thread
From: akr (Akira Tanaka) via ruby-core @ 2023-02-06  8:10 UTC (permalink / raw)
  To: ruby-core; +Cc: akr (Akira Tanaka)

Issue #19179 has been updated by akr (Akira Tanaka).


I think this is a good direction.

However, class and method names should be reviewed by matz.

I doubt that the current proposal has names good enough.
For example, "as_ancillary_data" is not in the convention of Ruby.
Ruby uses method names "to_*" for conversion methods.

----------------------------------------
Feature #19179: Support parsing SCM_CRED(ENTIALS) messages from ancillary messages
https://bugs.ruby-lang.org/issues/19179#change-101653

* Author: kjtsanaktsidis (KJ Tsanaktsidis)
* Status: Open
* Priority: Normal
----------------------------------------
## 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 th
 e 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!



-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [ruby-core:112300] [Ruby master Feature#19179] Support parsing SCM_CRED(ENTIALS) messages from ancillary messages
  2022-12-04  1:59 [ruby-core:111194] [Ruby master Feature#19179] Support parsing SCM_CRED(ENTIALS) messages from ancillary messages kjtsanaktsidis (KJ Tsanaktsidis)
  2023-01-25  7:12 ` [ruby-core:112023] " kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core
  2023-02-06  8:10 ` [ruby-core:112230] " akr (Akira Tanaka) via ruby-core
@ 2023-02-09 10:55 ` nobu (Nobuyoshi Nakada) via ruby-core
  2023-02-13  0:44 ` [ruby-core:112389] " kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core
  3 siblings, 0 replies; 5+ messages in thread
From: nobu (Nobuyoshi Nakada) via ruby-core @ 2023-02-09 10:55 UTC (permalink / raw)
  To: ruby-core; +Cc: nobu (Nobuyoshi Nakada)

Issue #19179 has been updated by nobu (Nobuyoshi Nakada).


Your PR has some `Socket::Credentials` class methods which are not in your proposal.
Why are `from_` methods needed as Ruby level methods?

----------------------------------------
Feature #19179: Support parsing SCM_CRED(ENTIALS) messages from ancillary messages
https://bugs.ruby-lang.org/issues/19179#change-101735

* Author: kjtsanaktsidis (KJ Tsanaktsidis)
* Status: Open
* Priority: Normal
----------------------------------------
## 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 th
 e 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!



-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [ruby-core:112389] [Ruby master Feature#19179] Support parsing SCM_CRED(ENTIALS) messages from ancillary messages
  2022-12-04  1:59 [ruby-core:111194] [Ruby master Feature#19179] Support parsing SCM_CRED(ENTIALS) messages from ancillary messages kjtsanaktsidis (KJ Tsanaktsidis)
                   ` (2 preceding siblings ...)
  2023-02-09 10:55 ` [ruby-core:112300] " nobu (Nobuyoshi Nakada) via ruby-core
@ 2023-02-13  0:44 ` kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core
  3 siblings, 0 replies; 5+ messages in thread
From: kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core @ 2023-02-13  0:44 UTC (permalink / raw)
  To: ruby-core; +Cc: kjtsanaktsidis (KJ Tsanaktsidis)

Issue #19179 has been updated by kjtsanaktsidis (KJ Tsanaktsidis).


It's true, the API currently exposed in that PR is a bit rough and un-ruby-like. Here's my idea for how the API should actually look:

* Expose a new struct `Socket::Credentials`.
  * It has the following fields: `pid`, `uid`, `gid`, `euid`, `egid`, and `groups`.
  * My PR currently has an extra field `source`, the name of the struct the credentials came from originally. This is done so that it can be printed in the `#inspect` output of `Socket::AncillaryData` and `Socket::Option`, so that the current output remains the same. I would remove `source` from the struct definition and instead keep it in a hidden ivar inaccessible from Ruby - it's not a field that Ruby code needs to know anything about.
  * It would have the following methods:
    * `::new` & `#initialize` - normal struct initialization routines (although I would add `keyword_init: true` to the struct definition, it's not currently in the PR).
    * `::for_current_process` - initializes a new `Socket::Credentials` structure with values obtained from `Process.pid` etc. This is currently called `::for_process` in my PR, but to me that name kind of implies you could pass a PID and get values for a different process.
    * `#inspect` - prints similar output to what `Socket::AncillaryData` and `Socket::Option` do today.
    * `#to_ancillary_data` - Constructs a `Socket::AncillaryData` of type `SCM_CREDENTIALS` (or `SCM_CREDS`) based on these credential
* Add some new methods to existing objects:
  * `Socket::AncillaryData#credentials` and `Socket::Option#credentials`. These would return a new `Socket::Credentials` struct containing the data from the ancdata/option. They would raise `ArgumentError` (or perhaps `TypeError` is more appropriate?) if the ancillary data or socket option is not of the correct type.

I would get rid of the `Socket::Credentials::from_*` class methods currently in my PR as well.

Does this sound like an improvement?

----------------------------------------
Feature #19179: Support parsing SCM_CRED(ENTIALS) messages from ancillary messages
https://bugs.ruby-lang.org/issues/19179#change-101831

* Author: kjtsanaktsidis (KJ Tsanaktsidis)
* Status: Open
* Priority: Normal
----------------------------------------
## 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 th
 e 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!



-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2023-02-13  0:45 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-12-04  1:59 [ruby-core:111194] [Ruby master Feature#19179] Support parsing SCM_CRED(ENTIALS) messages from ancillary messages kjtsanaktsidis (KJ Tsanaktsidis)
2023-01-25  7:12 ` [ruby-core:112023] " kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core
2023-02-06  8:10 ` [ruby-core:112230] " akr (Akira Tanaka) via ruby-core
2023-02-09 10:55 ` [ruby-core:112300] " nobu (Nobuyoshi Nakada) via ruby-core
2023-02-13  0:44 ` [ruby-core:112389] " kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).