Call to “hostnamectl set-hostname” gets blocked by AppArmor

Calls to “hostnamectl set-hostname someNewHostname” get blocked by apparmor

I am struggling to make use of hostname-control interface from within my snap to set hostname of the system. I have a Raspberry box. There is a snap running that contains a REST API server. Its .NET/C# running with Mono. The server has an endpoint that takes a string with a new hostname as an input from the user.

Once the endpoint is hit, it tries to execute the following code:

public static string SetSystemHostname(string newHostname)
    {
        var proc = new Process
        {
            StartInfo = new ProcessStartInfo
            {
    		    FileName = "/usr/bin/hostnamectl",
                Arguments = $"set-hostname {newHostname}",
                UseShellExecute = false,
                RedirectStandardOutput = true,
                CreateNoWindow = true
            }
        };

        proc.Start();
        proc.WaitForExit();

        return;
    }

The code works when the snap is running in devmode and fails when the snap is in strict confinement.

Using hostnamectl just to get the hostname also works in strict confinement.

developer@localhost:~$ snap version
snap    2.43.3
snapd   2.43.3
series  16
kernel  4.15.0-1057-raspi2


developer@localhost:~$ snap info --verbose rest-api
name:    rest-api
summary: api
health:
  status:  unknown
  message: health has not been set
publisher: –
license:   unset
description: |
  The new connected lighting solution.
services:
  rest-api:       simple, enabled, active
notes:
  private:           false
  confinement:       strict
  devmode:           false
  jailmode:          false
  trymode:           false
  enabled:           true
  broken:            false
  ignore-validation: false
base:         core18
refresh-date: today at 12:08 UTC
installed:    0.0.55f4a (x1) 22MB -

The snap is configures as daemon. From “snapcraft.yaml” file:

apps:
  rest-api:
    command: start-api.sh
    daemon: simple
    restart-condition: always
    plugs:
        - network-bind
        - hostname-control

I connect hostname-control interface manually.

sudo snap connect rest-api:hostname-control :hostname-control

snappy-debug output:

developer@localhost:~$ sudo journalctl --output=short --follow --all | sudo snappy-debug.security scanlog rest-api
= AppArmor =
Time: Mar 29 12:14:28
Log: apparmor="DENIED" operation="dbus_method_call"  bus="system" path="/org/freedesktop/hostname1" interface="org.freedesktop.hostname1" member="SetPrettyHostname" mask="send" name="org.freedesktop.hostname1" pid=8441 label="snap.rest-api.rest-api"
DBus access
Suggestion:
* try adding 'hostname-control' to 'plugs'

= AppArmor =
Time: Mar 29 12:14:28
Log: apparmor="DENIED" operation="open" profile="snap.rest-api.rest-api" name="/proc/1/environ" pid=8441 comm="hostnamectl" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
File: /proc/1/environ (read)
Suggestions:
* adjust program to not access '@{PROC}/@{pid}/environ'
* do nothing if using systemd utility (eg, timedatectl): 
* do nothing if program otherwise works properly

= AppArmor =
Time: Mar 29 12:14:28
Log: apparmor="DENIED" operation="open" profile="snap.rest-api.rest-api" name="/proc/1/sched" pid=8441 comm="hostnamectl" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
File: /proc/1/sched (read)
Suggestion:
* adjust program to not access '@{PROC}/@{pid}/sched'

= AppArmor =
Time: Mar 29 12:14:28
Log: apparmor="DENIED" operation="capable" profile="snap.rest-api.rest-api" pid=8441 comm="hostnamectl" capability=12  capname="net_admin"
Capability: net_admin
Suggestions:
* adjust program to not require 'CAP_NET_ADMIN' (see 'man 7 capabilities')
* add one of 'bluetooth-control, firewall-control, netlink-audit, netlink-connector, network-control' to 'plugs'
* do nothing if using systemd utility (eg, timedatectl): 
* do nothing ()

= AppArmor =
Time: Mar 29 12:14:28
Log: apparmor="DENIED" operation="dbus_method_call"  bus="system" path="/org/freedesktop/hostname1" interface="org.freedesktop.hostname1" member="SetPrettyHostname" mask="send" name="org.freedesktop.hostname1" pid=8441 label="snap.rest-api.rest-api"
DBus access
Suggestion:
* try adding 'hostname-control' to 'plugs'

It sounds like the interface isn’t connect. Please do:

$ sudo snap connect rest-api:hostname-control

then try again.

The interface is connected:

developer@localhost:~$ snap connections rest-api
Interface         Plug                           Slot           Notes
content           rest-api:commissioningdata  -              -
hostname-control  rest-api:hostname-control   -              -
network-bind      rest-api:network-bind       :network-bind  -

developer@localhost:~$ sudo snap connect rest-api:hostname-control

developer@localhost:~$ snap connections rest-api
Interface         Plug                           Slot               Notes
content           rest-api:commissioningdata  -                  -
hostname-control  rest-api:hostname-control   :hostname-control  manual
network-bind      rest-api:network-bind       :network-bind

Another peculiar observation is that setting the hostname works if I first call hostnamectl to get the hostname and then shortly after call hostnamectl set-hostname to set it.

“Shortly after” means no later than 30 seconds. I established that with a test script, the behavior is 100% reproducible on my setup:

...
Iteration: 28
Old hostname: dev-test-27   # Read out the hostname with hostnamectl
Sleeping for 27 seconds   # Wait for N seconds
New hostname: dev-test-28   # Attempt to set the hostname using hostnamectl set-hostname
Changed hostname: dev-test-28   # Read out the hostname with hostnamectl
Testcase: PASS   # If new hostname == changed hostname --> PASS

Iteration: 29
Old hostname: dev-test-28
Sleeping for 28 seconds
New hostname: dev-test-29
Changed hostname: dev-test-29
Testcase: PASS

Iteration: 30
Old hostname: dev-test-29
Sleeping for 29 seconds
New hostname: dev-test-30
Changed hostname: dev-test-30
Testcase: PASS

Iteration: 31
Old hostname: dev-test-30
Sleeping for 30 seconds   # Starts failing when the waiting time is >= 30 seconds
New hostname: dev-test-31
Changed hostname: dev-test-30
Testcase: FAIL

Iteration: 32
Old hostname: dev-test-30
Sleeping for 31 seconds
New hostname: dev-test-32
Changed hostname: dev-test-30
Testcase: FAIL
...

It looks like what is happening is that the service is DBus activated, the service doesn’t use AssumedAppArmorLabel=unconfined in its service file and the snapd rule has:

dbus (send)
    bus=system
    path=/org/freedesktop/hostname1
    interface=org.freedesktop.DBus.Properties
    member="Get{,All}",
...
dbus(receive, send)
    bus=system
    path=/org/freedesktop/hostname1
    interface=org.freedesktop.hostname1
    member=Set{,Pretty,Static}Hostname
    peer=(label=unconfined),

Notice how the ‘Get’ rule does not use peer=(label=unconfined), but the ‘Set’ rule does. This should explain why Get works, Set works after Get, but Set doesn’t work after the service stops.

Can you adjust /var/lib/snapd/apparmor/profiles/snap.rest-api.rest-api to change this rule from:

dbus(receive, send)
    bus=system
    path=/org/freedesktop/hostname1
    interface=org.freedesktop.hostname1
    member=Set{,Pretty,Static}Hostname
    peer=(label=unconfined),

to:

dbus(receive, send)
    bus=system
    path=/org/freedesktop/hostname1
    interface=org.freedesktop.hostname1
    member=Set{,Pretty,Static}Hostname,

(please notice the trailing comma), then do sudo apparmor_parser -r /var/lib/snapd/apparmor/profiles/snap.rest-api.rest-api and report back. This should now work for you, and we can adjust snapd accordingly.

I tried the workaround you suggested on my setup and it fixed the issue. Thank you for your help!

1 Like

Thank you for checking that. This is queued to be fixed in snapd 2.45.

FYI, I added these accesses in https://github.com/snapcore/snapd/pull/8443