AppArmor blocks sending signals on Ubuntu 25.04 host

Symptoms and Preliminary Investigation

On an Ubuntu 25.04 host running Incus 6.13 from Zabbly repo I created an Ubuntu 24.04 guest container, opened a shell to it and ran apt update… and it froze.

From another host shell, I checked the container’s process tree, and it looked like this:

ihor@mad-desktop:~$ incus exec noble-lxc -- ps af  
    PID TTY      STAT   TIME COMMAND
    245 pts/0    Ss+    0:00 /sbin/agetty -o -p -- \u --noclear --keep-baud - 115200,38400,9600 linux
    603 pts/2    Rs+    0:00 ps af
    267 pts/1    Ss     0:00 su -l
    278 pts/1    S      0:00  \_ -bash
    516 pts/1    S+     0:00      \_ apt update
    519 pts/1    S+     0:00          \_ apt update
    520 pts/1    S+     0:00              \_ sh -c -- [ ! -e /run/systemd/system ] || [ $(id -u) -ne 0 ] || systemctl start --no-block apt-news.service esm-cache.service >/dev/null 2>&
    522 pts/1    S+     0:00                  \_ systemctl start --no-block apt-news.service esm-cache.service
    523 pts/1    S+     0:00                      \_ /usr/bin/systemd-tty-ask-password-agent --watch

So it looks like systemctl is blocked by systemd-tty-ask-password-agent for some reason.
There are many complaints on the Internet about systemd-tty-ask-password-agent being a blocker, but this time it wasn’t the case.

Here’s an interesting picture that I got from running strace on the host:

19596 19:07:45.041859 execve("/usr/bin/systemctl", ["systemctl", "start", "--no-block", "apt-news.service", "esm-cache.service"], 0x5f98d387fc30 /* 19 vars */) = 0
...
19596 19:07:45.120102 getpid()                = 522 /* 19596 in strace's PID NS */
...
19596 19:07:45.194694 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x740668dbdb90) = 523 /* 19597 in strace's PID NS */
...
19597 19:07:45.343764 execve("/usr/bin/systemd-tty-ask-password-agent", ["/usr/bin/systemd-tty-ask-passwor"..., "--watch"], 0x7ffc3897b0a8 /* 19 vars */) = 0
...
19596 19:07:45.378738 kill(523 /* 19597 in strace's PID NS */, SIGTERM) = -1 EACCES (Permission denied)
19596 19:07:45.378819 waitid(P_PID, 523 /* 19597 in strace's PID NS */,  <detached ...>

Here’s what’s happening:

  1. systemctl (PID 522 in container, PID 19596 on host) spawns systemd-tty-ask-password-agent (PID 523 in container, PID 19597 on host) just in case it would need to handle password input from the user;
  2. it successfully starts apt-news and esm-cache services (not shown in this picture);
  3. systemctl then decides it no longer needs systemd-tty-ask-password-agent and tries to kill it, but is being forbidden from doing so (EACCESS);
  4. it then starts to waitid for systemd-tty-ask-password-agent to die out of old age, but it never happens, hence the freeze we observe.

Steps to Reproduce

  1. On an Ubuntu 25.04 host install incus 6.13 from Zabbly repo:

    ihor@mad-desktop:~$ apt policy incus
    incus:
      Installed: 1:6.13-ubuntu24.04-202506030459
      Candidate: 1:6.13-ubuntu24.04-202506030459
      Version table:
     *** 1:6.13-ubuntu24.04-202506030459 500
            500 https://pkgs.zabbly.com/incus/stable noble/main amd64 Packages
            100 /var/lib/dpkg/status
         1:6.13-ubuntu24.04-202506021921 500
            500 https://pkgs.zabbly.com/incus/stable noble/main amd64 Packages
         6.0.3-4 500
            500 http://pl.archive.ubuntu.com/ubuntu plucky/universe amd64 Packages
    
  2. Create an unprivileged Ubuntu 24.04 guest container:

    ihor@mad-desktop:~$ incus launch images:ubuntu/noble noble-lxc --type=aws:t2.micro
    
  3. Enter the container:

    ihor@mad-desktop:~$ incus shell noble-lxc
    
  4. Spawn a background process, then try to kill it:

    root@noble-lxc:~#yes > /dev/null &
    [1] 354
    root@noble-lxc:~#kill $!
    -bash: kill: (354) - Permission denied
    

The very same steps when on an Ubuntu 24.04 do work: you can kill the yes process just fine.

This leads me to thinking that something has changed from 24.04 to 25.04.

Investigation

Ubuntu 25.04

After trial and error, I narrowed down the problem to AppArmor blocking the syscall:

ihor@mad-desktop:~$ journalctl --boot --grep=apparmor --since="-1 minute"
Jun 08 21:39:03 mad-desktop kernel: audit: type=1400 audit(1749411543.030:5475636): apparmor="DENIED" operation="signal" class="signal" profile="incus-noble-lxc_</var/lib/incus>" pid=233658 comm="bash" requested_mask="send" denied_mask="send" signal=term peer="incus-noble-lxc_</var/lib/incus>//&unconfined"

My understanding of this log message is that bash was denied to send a signal to a process with AppArmor label incus-noble-lxc_</var/lib/incus>//&unconfined.

So I checked the signal related rules in AppArmor profile for the container:

ihor@mad-desktop:~$ sudo grep -E '(^profile|signal)' /var/lib/incus/security/apparmor/profiles/incus-noble-lxc
profile "incus-noble-lxc_</var/lib/incus>" flags=(attach_disconnected,mediate_deleted) {
  # Allow normal signal handling
  signal (receive),
  signal peer=@{profile_name},

The variable @{profile_name} should expand to incus-noble-lxc_</var/lib/incus> which does not equal to incus-noble-lxc_</var/lib/incus>//&unconfined I saw in the log.

If I understand correctly, //& means profile intersection, unconfined is an “everything is allowed” profile, so in theory both incus-noble-lxc_</var/lib/incus> and incus-noble-lxc_</var/lib/incus>//&unconfined should mean exactly the same, but it looks like AppArmor wants a literal string equality.

Ubuntu 24.04

Since it works on Ubuntu 24.04, I wanted to check what peer would be seen in the audit logs. So I adjusted AppArmor profile to deny sending SIGTERM:

root@incus-host:~# incus config set noble-lxc raw.apparmor="audit deny signal (send) set=("term"),"

Then I did the yes kill dance and looked at the logs:

root@incus-host:~# journalctl --boot --grep=apparmor --since="-1 minute"
Jun 08 20:58:28 incus-host kernel: audit: type=1400 audit(1749416308.401:131): apparmor="DENIED" operation="signal" class="signal" profile="incus-noble-lxc_</var/lib/incus>" pid=4840 comm="bash" requested_mask="send" denied_mask="send" signal=term peer="incus-noble-lxc_</var/lib/incus>"

Conclusion

On Ubuntu 24.04 the peer is $CONTAINER_NAME_</var/lib/incus>, on Ubuntu 25.04 the peer is $CONTAINER_NAME_</var/lib/incus>//&unconfined. The latter doesn’t match the rule in AppArmor profile, so the signal is denied.

Temporary Fix

I suppose not being able to kill your own children can cause a lot of software to misbehave, not just apt or systemd.

I don’t know what caused this change, but for now, I ended up with a profile containing an extra AppArmor profile entry:

incus profile create apparmor_fix
incus profile set apparmor_fix raw.apparmor="signal peer=@{profile_name}//&unconfined,"

Then it can either be used to attach to existing containers:

incus attach apparmor_fix noble-lxc

or to launch new containers:

incus launch images:ubuntu/noble noble-lxc2 --type=aws:t2.micro --profile default --profile apparmor_fix

In the long run, it would be nice to either have this entry in the official AppArmor template that Incus uses, or figure out why does Ubuntu 25.04 behave differently than Ubuntu 24.04.