Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sd-bus fails to parse dict entries with non-string key elements #32904

Open
mrc0mmand opened this issue May 17, 2024 · 0 comments
Open

sd-bus fails to parse dict entries with non-string key elements #32904

mrc0mmand opened this issue May 17, 2024 · 0 comments
Labels
bug 🐛 Programming errors, that need preferential fixing sd-bus

Comments

@mrc0mmand
Copy link
Member

mrc0mmand commented May 17, 2024

Came across this in busctl while debugging something dbus-broker related.

The dbus-broker's org.freedesktop.DBus.Debug.Stats interface implements GetStats method, which yields following hierarchy:

dbus-send ...
# dbus-send --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.Debug.Stats.GetStats method return time=1715966260.487060 sender=org.freedesktop.DBus -> destination=:1.12 serial=4294967295 reply_serial=2
   array [
      dict entry(
         string "org.bus1.DBus.Debug.Stats.PeerAccounting"
         variant             array [
               struct {
                  string ":1.1"
                  array [
                     dict entry(
                        string "UnixUserID"
                        variant                            uint32 0
                     )
                     dict entry(
                        string "ProcessID"
                        variant                            uint32 1218
                     )
                     dict entry(
                        string "UnixGroupIDs"
                        variant                            array [
                              uint32 0
                           ]
                     )
                     dict entry(
                        string "LinuxSecurityLabel"
                        variant                            array of bytes "unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023" + \0
                     )
                  ]
                  array [
                     dict entry(
                        string "NameObjects"
                        uint32 1
                     )
                     dict entry(
                        string "MatchBytes"
                        uint32 4180
                     )
                     dict entry(
                        string "Matches"
                        uint32 3
                     )
                     dict entry(
                        string "ReplyObjects"
                        uint32 0
                     )
                     dict entry(
                        string "IncomingBytes"
                        uint32 16
                     )
                     dict entry(
                        string "IncomingFds"
                        uint32 0
                     )
                     dict entry(
                        string "OutgoingBytes"
                        uint32 0
                     )
                     dict entry(
                        string "OutgoingFds"
                        uint32 0
                     )
                     dict entry(
                        string "ActivationRequestBytes"
                        uint32 0
                     )
                     dict entry(
                        string "ActivationRequestFds"
                        uint32 0
                     )
                  ]
               }
               struct {
                  string ":1.2"
                  array [
                     dict entry(
                        string "UnixUserID"
                        variant                            uint32 0
                     )
                     dict entry(
                        string "ProcessID"
                        variant                            uint32 1576
                     )
                     dict entry(
                        string "UnixGroupIDs"
                        variant                            array [
                              uint32 0
                           ]
                     )
                     dict entry(
                        string "LinuxSecurityLabel"
                        variant                            array of bytes "unconfined_u:unconfined_r:unconfined_dbusd_t:s0-s0:c0.c1023" + \0
                     )
                  ]
                  array [
                     dict entry(
                        string "NameObjects"
                        uint32 0
                     )
                     dict entry(
                        string "MatchBytes"
                        uint32 0
                     )
                     dict entry(
                        string "Matches"
                        uint32 0
                     )
                     dict entry(
                        string "ReplyObjects"
                        uint32 0
                     )
                     dict entry(
                        string "IncomingBytes"
                        uint32 16
                     )
                     dict entry(
                        string "IncomingFds"
                        uint32 0
                     )
                     dict entry(
                        string "OutgoingBytes"
                        uint32 0
                     )
                     dict entry(
                        string "OutgoingFds"
                        uint32 0
                     )
                     dict entry(
                        string "ActivationRequestBytes"
                        uint32 0
                     )
                     dict entry(
                        string "ActivationRequestFds"
                        uint32 0
                     )
                  ]
               }
               struct {
                  string ":1.12"
                  array [
                     dict entry(
                        string "UnixUserID"
                        variant                            uint32 0
                     )
                     dict entry(
                        string "ProcessID"
                        variant                            uint32 1691
                     )
                     dict entry(
                        string "UnixGroupIDs"
                        variant                            array [
                              uint32 0
                              uint32 0
                           ]
                     )
                     dict entry(
                        string "LinuxSecurityLabel"
                        variant                            array of bytes "unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023" + \0
                     )
                  ]
                  array [
                     dict entry(
                        string "NameObjects"
                        uint32 0
                     )
                     dict entry(
                        string "MatchBytes"
                        uint32 0
                     )
                     dict entry(
                        string "Matches"
                        uint32 0
                     )
                     dict entry(
                        string "ReplyObjects"
                        uint32 0
                     )
                     dict entry(
                        string "IncomingBytes"
                        uint32 0
                     )
                     dict entry(
                        string "IncomingFds"
                        uint32 0
                     )
                     dict entry(
                        string "OutgoingBytes"
                        uint32 0
                     )
                     dict entry(
                        string "OutgoingFds"
                        uint32 0
                     )
                     dict entry(
                        string "ActivationRequestBytes"
                        uint32 0
                     )
                     dict entry(
                        string "ActivationRequestFds"
                        uint32 0
                     )
                  ]
               }
            ]
      )
      dict entry(
         string "org.bus1.DBus.Debug.Stats.UserAccounting"
         variant             array [
               struct {
                  uint32 0
                  array [
                     struct {
                        string "Bytes"
                        uint32 276434940
                        uint32 276447232
                     }
                     struct {
                        string "Fds"
                        uint32 3290337277
                        uint32 3290337280
                     }
                     struct {
                        string "Matches"
                        uint32 705032701
                        uint32 705032704
                     }
                     struct {
                        string "Objects"
                        uint32 16777212
                        uint32 16777216
                     }
                  ]
                  array [
                     dict entry(
                        uint32 0
                        array [
                           dict entry(
                              string "Bytes"
                              uint32 12292
                           )
                           dict entry(
                              string "Fds"
                              uint32 3
                           )
                           dict entry(
                              string "Matches"
                              uint32 3
                           )
                           dict entry(
                              string "Objects"
                              uint32 4
                           )
                        ]
                     )
                  ]
               }
            ]
      )
   ]

However, busctl really struggles with this, and straight up refuses to convert it to JSON:

# busctl call -j org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.Debug.Stats GetStats
# echo $?
1

After some spelunking, the issue seems to be in this code:

systemd/src/shared/json.c

Lines 738 to 739 in 3acc318

if (!json_variant_is_string(c))
return -EINVAL; /* Every second one needs to be a string, as it is the key name */

The statement in the comment is wrong. The D-Bus spec says this:

A DICT_ENTRY works exactly like a struct, but rather than parentheses it uses curly braces, and it has more restrictions. The restrictions are: it occurs only as an array element type; it has exactly two single complete types inside the curly braces; the first single complete type (the "key") must be a basic type rather than a container type. Implementations must not accept dict entries outside of arrays, must not accept dict entries with zero, one, or more than two fields, and must not accept dict entries with non-basic-typed keys. A dict entry is always a key-value pair.

That is, the first element in a dict entry must be a basic type, which is less strict than just a string. So our JSON code trips over when parsing this part of the aforementioned dbus message:

array [
   dict entry(
      uint32 0
      array [
         dict entry(
            string "Bytes"
            uint32 12292
         )
         ...
      ]
   )
   ...

Since the first element in the outermost dict entry is not a string, but an unsigned integer.

@mrc0mmand mrc0mmand added sd-bus bug 🐛 Programming errors, that need preferential fixing labels May 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Programming errors, that need preferential fixing sd-bus
Development

No branches or pull requests

1 participant