diwic / dbus-rs

D-Bus binding for the Rust language
Other
591 stars 133 forks source link

monitor system bus without root privileges #433

Closed acheronfail closed 1 year ago

acheronfail commented 1 year ago

I'm trying to monitor the system bus and am having some issues.

Using the monitor example as a base, I've created a repository here: https://github.com/acheronfail/dbus-system-monitor/ (just adds a type and interface to the match rule).

The problem

I want to be able to do what dbus-monitor does when it can't BecomeMonitor:

# run as non-root user
$ dbus-monitor --system "type='signal',interface='org.freedesktop.NetworkManager'"
dbus-monitor: unable to enable new-style monitoring: org.freedesktop.DBus.Error.AccessDenied: "Rejected send message, 1 matched rules; type="method_call", sender=":1.123" (uid=1000 pid=92012 comm="dbus-monitor --system type='signal',interface='org") interface="org.freedesktop.DBus.Monitoring" member="BecomeMonitor" error name="(unset)" requested_reply="0" destination="org.freedesktop.DBus" (bus)". Falling back to eavesdropping.
signal time=1684331902.119610 sender=org.freedesktop.DBus -> destination=:1.123 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
   string ":1.123"
...
...

As you can see, its BecomeMonitor request fails if the user is not root, and then it "falls back" to eavesdropping. The monitor example also does this, but if I try to run it, it fails:

# dbus-rs (via acheronfail/dbus-system-monitor)
# run as non-root user
$ cargo run --quiet
falling back to eavesdrop: Rejected send message, 1 matched rules; type="method_call", sender=":1.125" (uid=1000 pid=92482 comm="target/debug/dbus-system-monitor") interface="org.freedesktop.DBus.Monitoring" member="BecomeMonitor" error name="(unset)" requested_reply="0" destination="org.freedesktop.DBus" (bus)

thread 'main' panicked at 'add_match failed: D-Bus error: rejected attempt to call AddMatch by connection :1.125 (uid=1000 pid=92482 comm="target/debug/dbus-system-monitor") with uid 1000 (org.freedesktop.DBus.Error.AccessDenied)', src/main.rs:49:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

So, the monitor example also falls back to eavesdrop=true, but it fails when doing so.

Investigations

I've been using strace to compare, and I've found that this crate sends a very slightly different message to dbus, observe:

# dbus-monitor
sendmsg(3, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="l\1\0\1L\0\0\0\3\0\0\0\177\0\0\0\1\1o\0\25\0\0\0/org/freedesktop/DBus\0\0\0\6\1s\0\24\0\0\0org.freedesktop.DBus\0\0\0\0\2\1s\0\24\0\0\0org.freedesktop.DBus\0\0\0\0\3\1s\0\10\0\0\0AddMatch"..., iov_len=144}, {iov_base="G\0\0\0eavesdrop=true,type='signal',interface='org.freedesktop.NetworkManager'\0", iov_len=76}], msg_iovlen=2, msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 220
# dbus-rs
sendmsg(3, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="l\1\0\1N\0\0\0\3\0\0\0\177\0\0\0\1\1o\0\25\0\0\0/org/freedesktop/DBus\0\0\0\6\1s\0\24\0\0\0org.freedesktop.DBus\0\0\0\0\2\1s\0\24\0\0\0org.freedesktop.DBus\0\0\0\0\3\1s\0\10\0\0\0AddMatch"..., iov_len=144}, {iov_base="I\0\0\0type='signal',interface='org.freedesktop.NetworkManager',eavesdrop='true'\0", iov_len=78}], msg_iovlen=2, msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 222

Specifically, compare the second item in the sendmsg calls:

# dbus-monitor
{iov_base="G\0\0\0eavesdrop=true,type='signal',interface='org.freedesktop.NetworkManager'\0", iov_len=76}
# dbus-rs
{iov_base="I\0\0\0type='signal',interface='org.freedesktop.NetworkManager',eavesdrop='true'\0", iov_len=78}

Observations

There are two differences:

  1. eavesdrop=true vs eavesdrop='true': dbus-monitor doesn't quote true. I'm not convinced this is the issue
  2. the first character of the message is G in dbus-monitor, but in dbus-rs it's I: to me, this seems like a likely case?

I've been trying to get dbus-rs to send a message with G instead of I, but I'm getting lost in all the impls and macros inside dbus-rs :sweat_smile:

Do you have any idea what could be going on here? Or anything to point me in the right direction?

diwic commented 1 year ago

The I instead of the G is probably the length of the message, with two quotes the messages becomes two bytes longer and I is two letters behind G in the alphabet. See if eavesdrop=true makes any difference.

Also feel free to revive https://github.com/diwic/dbus-rs/pull/392

diwic commented 1 year ago

As a side note, the spec thinks the true and false should be in quotes: https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules

acheronfail commented 1 year ago

The I instead of the G is probably the length of the message, with two quotes the messages becomes two bytes longer and I is two letters behind G in the alphabet. See if eavesdrop=true makes any difference.

Ah, you're right! Can't believe I missed that.

As a side note, the spec thinks the true and false should be in quotes: dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules

Ah sweet, so we're doing the right thing here.


Also feel free to revive https://github.com/diwic/dbus-rs/pull/392

Alright, so I looked into that, and it looks like the previous thoughts were that dbus-monitor just sets up a match with an empty string if BecomeMonitor fails but I don't think that's actually the case with what I'm doing.

As I understand dbus-monitor.c, it does this:

if (BecomeMonitor succeeds) {
  // success
}
else if (We have a filter) {
  // try: `eavesdrop=true' + filter
  // if that fails, try: filter
  // if that fails, error
} else {
  // try `eavesdrop=true` by itself
  // if that fails, try: "" (empty string
  // if that fails, error
}

Since my "filter" is type='signal',interface='org.freedesktop.NetworkManager' I'm hitting that middle block in the if/else chain, and that succeeds in dbus-monitor, but it fails as described above when I try to do the same with this crate. :thinking:

acheronfail commented 1 year ago

Ah, actually I got it working!

falling back to eavesdrop: Rejected send message, 1 matched rules; type="method_call", sender=":1.81" (uid=1000 pid=12764 comm="target/debug/dbus-system-monitor") interface="org.freedesktop.DBus.Monitoring" member="BecomeMonitor" error name="(unset)" requested_reply="0" destination="org.freedesktop.DBus" (bus)

Got message: Message { Type: Signal, Path: "/org/freedesktop/NetworkManager", Interface: "org.freedesktop.NetworkManager", Member: "StateChanged", Sender: ":1.5", Serial: 845, Args: [30] }
Got message: Message { Type: Signal, Path: "/org/freedesktop/NetworkManager", Interface: "org.freedesktop.NetworkManager", Member: "StateChanged", Sender: ":1.5", Serial: 876, Args: [20] }
Got message: Message { Type: Signal, Path: "/org/freedesktop/NetworkManager", Interface: "org.freedesktop.NetworkManager", Member: "StateChanged", Sender: ":1.5", Serial: 920, Args: [40] }
Got message: Message { Type: Signal, Path: "/org/freedesktop/NetworkManager", Interface: "org.freedesktop.NetworkManager", Member: "StateChanged", Sender: ":1.5", Serial: 968, Args: [60] }

So, my example was doing this:

  1. Try BecomeMonitor
  2. if that fails, try MatchRule with eavesdrop

But, as I said above, dbus-monitor does this:

  1. Try BecomeMonitor
  2. If that fails, try with eavesdrop
  3. If that fails, try without eavesdrop.

If I don't set eavesdrop in my example above, then it works the same as dbus-monitor.

I think the most confusing part was dbus-monitor's output, since it appears to be working using eavedrop, but in reality eavesdrop also failed, and it's just using regular matching.

Thanks for all your help on this! I'm going to close this issue since I've figured out how it works.