0

I am using an embedded system that has multiple users like 'root' and 'user1'. I am running a c++ binary logged in as 'user1' and It fails to start / stop a service with a permission error. The same binary when running in root works fine. Here is the code:

#include <iostream>
#include <systemd/sd-bus.h>


static void SDCallMethodSS(
  sd_bus* bus,
  const std::string& name,
  const std::string& method)
{
  sd_bus_error err = SD_BUS_ERROR_NULL;
  sd_bus_message* msg = nullptr;
  int r;

  r = sd_bus_call_method(bus,
      "org.freedesktop.systemd1",
      "/org/freedesktop/systemd1",
      "org.freedesktop.systemd1.Manager",
      method.c_str(),
      &err,
      &msg,
      "ss",
      name.c_str(),  "replace" );

  if (r < 0)
  {
    std::string err_str("Could not send " + method +
                        " command to systemd for service: " + name +
                        ". Error: " + err.message );

    sd_bus_error_free(&err);
    sd_bus_message_unref(msg);
    throw std::runtime_error(err_str);
  }

  char* response;
  r = sd_bus_message_read(msg, "o", &response);
  if (r < 0)
  {
          std::cerr<< "Failed to parse response message: " << strerror(-r) << std::endl;;
  }

  sd_bus_error_free(&err);
  sd_bus_message_unref(msg);
}

int main() {
  int r;
  sd_bus *bus = NULL;

  r = sd_bus_open_system(&bus);
  if (r < 0) {
          std::cerr<< "Failed to connect to system bus: " << strerror(-r) << std::endl;
    return -1;
  }

  try{
    SDCallMethodSS(bus, std::string("foo-daemon.service"), std::string("StopUnit"));
  } catch (std::exception& e) {
    std::cout << "Exception in SDCallMethodSS(): " << e.what() << std::endl;
    return -2;
  }
}

Foo-daemon is a dummy program:

#include <unistd.h>

int main()
{
  while(1){
    sleep(1);
  }

}

The service file is simple:

[Unit]
Description=Foo

[Service]
ExecStart=/usr/local/bin/foo-daemon

[Install]
WantedBy=multi-user.target

Service file is loaded into /etc/systemd/system Output for 'user1' is:

Exception in SDCallMethodSS(): Could not send StopUnit command to systemd for service: foo-daemon.service. Error: Permission denied

How do I address the permissions issue for 'user1'

0

1 Answer 1

2

Your problem starts here:

r = sd_bus_open_system(&bus);

That opens the system's bus. That would lead to the same behavior as if you ran

user1@machine:~$ systemctl ...

It doesn't matter whether you are using the sd-bus API or systemctl, systemd will authenticate you the same way. user1 does not have permission to start/stop units.


Alternative 1: --user bus

One alternative is to use:

r = sd_bus_open_user(&bus);

This is similar to using systemctl --user ..., but your process will have the same permissions as user1 and will only run on user1's bus.


Alternative 2: polkit rules (user-permission)

We need to configure systemd to allow user1 to start/stop units on the system bus. This is done through polkit

If you are on a Debian-based system (polkit < 106) create a rule by creating a *.pkla file:

/etc/polkit-1/localauthority/50-local.d/service-auth.pkla
---
[Allow user1 to start/stop/restart services]
Identity=unix-user:user1
Action=org.freedesktop.systemd1.manage-units
ResultActive=yes

Alternative 3: polkit rules (service-specific permission)

If you are on Redhat/Arch based systems (polkit >=106), then you have a javascript-type syntax which lets you be a bit more specific. In this case, you could allow any user to manage foo-daemon.service with a *.rules file:

/etc/polkit-1/rules.d/foo-daemon.rules
---
polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.systemd1.manage-units") {
        if (action.lookup("unit") == "foo-daemon.service") {
            var verb = action.lookup("verb");
            if (verb == "start" || verb == "stop" || verb == "restart") {
                return polkit.Result.YES;
            }
        }
    }
});

Alternative 4: polkit rules (group-permission)

A solution I like to use is granting members of a group permission to manage units. Then as long as your user is a member of this group, they will be able to systemctl {start,stop,restart} ... or sd_bus_open_system(...)

There is an answer on how to do this here:

systemd start as unprivileged user in a group

5
  • Thank you very much for the detailed explanation. However, unfortunately, I am on an embedded system running yocto - open embedded distro. My distro doesn't have polkit in it. Commented Sep 9, 2022 at 19:13
  • Is there any way to open up the system bus completely for any user ? This is an embedded system and the user accounts are not typical users. Can I losen up the systemd system bus? Commented Sep 9, 2022 at 19:56
  • Possibly, but it wouldn't be in the sd-bus API. That's a runtime API which we shouldn't be able to use to elevate our privileges. The change would need to be in systemd's configuration. I'm guessing something related to PAM. Commented Sep 10, 2022 at 6:52
  • Browsing freedesktop.org/software/systemd/man, I see 15 man pages which would be relevant. That's going to take a few hours to read. I'll ammend the answer if I find anything. Commented Sep 10, 2022 at 6:53
  • I've spent as much time as I'm willing to get you an answer. man systemd-logind.service explicitly says it uses polkit for user operations. It does not appear to be configurable. I suspect you'd need to replace systemd-logind with your own home-built solution. Commented Sep 10, 2022 at 7:15

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.