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(&amp;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'

preetam
  • 117

1 Answers1

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

Stewart
  • 13,677
  • 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. – preetam Sep 09 '22 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? – preetam Sep 09 '22 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. – Stewart Sep 10 '22 at 06:52
  • Browsing https://www.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. – Stewart Sep 10 '22 at 06: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. – Stewart Sep 10 '22 at 07:15