AA_CHANGE_PROFILE(2)

πŸ’… AA_CHANGE_PROFILE β€” the AppArmor era (section 2 of the manual, bestie)

okay so this is page AA_CHANGE_PROFILE(2) from the AppArmor manual set, and we're about to get into it.


✨ NAME ✨

aa_change_profile, aa_change_onexec β€” change a tasks profile πŸ¦‹ (yes that's really what it does, no cap)


🌸 SYNOPSIS 🌸

#include <sys/apparmor.h>

int aa_change_profile(const char *profile);

int aa_change_onexec(const char *profile);

and like, link with -lapparmor when you compile, obviously 🫢


πŸ’– DESCRIPTION πŸ’–

So. An AppArmor profile applies to an executable program. But what if one part of your program needs different access permissions than the other parts? Then that part can literally "change profile" to a different profile. iconic behaviour honestly.

To change into a new profile it can use the aa_change_profile() function β€” you just pass in a pointer to the profile you want to transition to. And listen: confined programs that wanna use aa_change_profile() need to have rules permitting changing to the named profile. It's giving "you need permission to get permission." See apparmor.d(8) for the deets.

Now, if a program wants to return out of the current profile back to the original profile, it may use aa_change_hat(2). Otherwise? The two profiles must have rules permitting changing between the two profiles. periodt πŸ’…

⚠️ Ngl this part matters: open file descriptors may not be remediated after a call to aa_change_profile(), so the calling program must close(2) open file descriptors to ensure they are not available after calling aa_change_profile(). Since aa_change_profile() is typically used just before execve(2), you may want to use open(2) or fcntl(2) with close-on-exec. πŸŽ€

And the aa_change_onexec() function? It's like the aa_change_profile() function except it specifies that the profile transition should take place on the next exec instead of immediately. The delayed profile change takes precedence over any exec transition rules within the confining profile. Delaying the profile boundary has a couple of advantages: it removes the need for stub transition profiles, and the exec boundary is a natural security layer where potentially sensitive memory is unmapped. lowkey genius 🧚


πŸ”₯ RETURN VALUE πŸ”₯

On success, zero is returned. On error, -1 is returned, and errno(3) is set appropriately. that's it that's the whole vibe


πŸ’€ ERRORS πŸ’€

  • EINVAL β€” The apparmor kernel module is not loaded, neither a profile nor a namespace was specified, or the communication via the /proc/*/attr/current file did not conform to protocol. 😭
  • ENOMEM β€” Insufficient kernel memory was available.
  • EPERM β€” The calling application is confined by apparmor and the no_new_privs bit is set.
  • EACCES β€” The task does not have sufficient permissions to change its domain. rude but fair.
  • ENOENT β€” The specified profile does not exist, or is not visible from the current Namespace. it's literally not there bestie.

πŸŽ€ EXAMPLE πŸŽ€

The following example shows a simple, if contrived, use of aa_change_profile(); a typical use of aa_change_profile() will aa_change_profile() just before an execve(2) so that the new child process is permanently confined. (yes the original sentence repeats itself like that, I don't make the rules πŸ’œ)

        #include <stdlib.h>
        #include <string.h>
        #include <sys/apparmor.h>
        #include <sys/types.h>
        #include <sys/stat.h>
        #include <fcntl.h>
        #include <stdio.h>
        #include <unistd.h>

        int main(int argc, char * argv[])
        {
                int fd;
                char buf[10];
                char *execve_args[4];

                printf("Before aa_change_profile():\n");
                if ((fd=open("/etc/passwd", O_RDONLY)) < 0) {
                       perror("Failure opening /etc/passwd");
                       return 1;
                }

                /* Confirm for ourselves that we can really read /etc/passwd */
                memset(&buf, 0, 10);
                if (read(fd, &buf, 10) == -1) {
                        perror("Failure reading /etc/passwd");
                        return 1;
                }
                buf[9] = '\0';
                printf("/etc/passwd: %s\n", buf);
                close(fd);

                printf("After aa_change_profile():\n");

                /* change profile to the "i_cant_be_trusted_anymore" profile, which
                 * should not have read access to /etc/passwd. */
                if (aa_change_profile("i_cant_be_trusted_anymore") < 0) {
                    perror("Failure changing profile -- aborting");
                    _exit(1);
                }

                /* confirm that we cannot read /etc/passwd */
                execve_args[0] = "/usr/bin/head";
                execve_args[1] = "-1";
                execve_args[2] = "/etc/passwd";
                execve_args[3] = NULL;
                execve("/usr/bin/head", execve_args, NULL);
                perror("execve");
                _exit(1);
        }

This code example requires a profile similar to the following to be loaded with apparmor_parser(8):

        profile i_cant_be_trusted_anymore {
            /etc/ld.so.cache      mr,
            /lib/ld-*.so*         mrix,
            /lib/libc*.so*        mr,

            /usr/bin/head ix,
        }

The output when run:

        $ /tmp/change_p
        Before aa_change_profile():
        /etc/passwd: root:x:0:
        After aa_change_profile():
        /usr/bin/head: cannot open `/etc/passwd' for reading: Permission denied
        $

And if /tmp/change_p is to be confined as well, then the following profile can be used (in addition to the one for i_cant_be_trusted_anymore, above):

        # Confine change_p to be able to read /etc/passwd and aa_change_profile()
        # to the 'i_cant_be_trusted_anymore' profile.
        /tmp/change_p {
            /etc/ld.so.cache          mr,
            /lib/ld-*.so*             mrix,
            /lib/libc*.so*            mr,

            /etc/passwd               r,

            # Needed for aa_change_profile()
            /usr/lib/libapparmor*.so* mr,
            /proc/[0-9]*/attr/current w,
            change_profile -> i_cant_be_trusted_anymore,
        }

🌈 BUGS 🌈

None known. If you find any, please report them at <https://gitlab.com/apparmor/apparmor/-/issues>. πŸ›

Note that using aa_change_profile(2) without execve(2) provides no memory barriers between different areas of a program; if address space separation is required, then separate processes should be used. like, actually read that one, it's important πŸ™ˆ

⭐ SEE ALSO ⭐

apparmor(7), apparmor.d(5), apparmor_parser(8), aa_change_hat(2) and <https://wiki.apparmor.net>.


okay bye bestie, go read the wiki page, mwah 🩷✨ β€” this has been AppArmor 4.1.8, dated 2026-10-18, from your favourite manual page AA_CHANGE_PROFILE(2). slay πŸ’…πŸ‘‘

πŸ“¦ where this page comes from
  • arch rolling: apparmor 4.1.8-2 Β· GPL-2.0-only AND LGPL-2.0-only AND LGPL-2.1-only
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms