Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On the fly, request/permission based profile building #5079

Open
romelsalwi opened this issue Mar 28, 2022 · 15 comments
Open

On the fly, request/permission based profile building #5079

romelsalwi opened this issue Mar 28, 2022 · 15 comments
Labels
enhancement New feature request

Comments

@romelsalwi
Copy link

romelsalwi commented Mar 28, 2022

I have been using firejail for couple of months. At first it was quite impressive and was exactly what I was looking for building restriction around applications. But what I lack is the way of finding the exact resource which the application requires and doing my own due diligence to solve various tweaks.

For instance, keepassxc, if jailed causes issues for chromium; jailing free tube restricts usage of external player like mpv; restricting mpv won't allow me to use btfs and stream and watch on the fly.

What I would like to see is building permissions for a jailed app. If I'm trying to do something which is in conflict with the rules, ask, and update the profile or local file depending on my choice. In this way, building profile would be easy, like using uBO on hard mode, and I don't mind that cumbersome effort as much as how much intuitive it is!

@romelsalwi romelsalwi changed the title Ont the fly, request/permission based profile building On the fly, request/permission based profile building Mar 28, 2022
@glitsj16
Copy link
Collaborator

For instance, keepassxc, if jailed causes issues for chromium; jailing free tube restricts usage of external player like mpv; restricting mpv won't allow me to use btfs and stream and watch on the fly.

Please open separate issues for these problems you're facing. It helps us improve the profiles for all users, even if your use cases are very personal. The freetube profile for example is designed for using the internal player. So you would need to add mpv to private-bin to support that. This is just one example, but it's worth the effort IMO.

What I would like to see is building permissions for a jailed app.

You can use the --build or --build=profile-file options to create a whitelisting profile for any app you want. Based on the result this can be hardenend further to get it as tight as possible without loosing functionality. Have you looked into that workflow yet?

@romelsalwi
Copy link
Author

romelsalwi commented Mar 28, 2022

First of all, thanks for responding! It means a lot!

Please open separate issues for these problems you're facing. It helps us improve the profiles for all users, even if your use cases are very personal. The freetube profile for example is designed for using the internal player. So you would need to add mpv to private-bin to support that. This is just one example, but it's worth the effort IMO.

I might understand that you guys are trying to build a library for the application. But there are so many of them. My intend wasn't to post an issue but to illustrate how you can enhance upon the profiling aspect of firejail.

You can use the --build or --build=profile-file options to create a whitelisting profile for any app you want. Based on the result this can be hardenend further to get it as tight as possible without loosing functionality. Have you looked into that workflow yet?

They don't work as intended. As a refresher I just tried building new profiles with it. Ultimately I had to copy the template and make the profile from scratch. But then I was expecting to refine the profile through build, which didn't go so well. I'm sure I'm doing something wrong.

For instance:
I made a profile for Joplin
firejail --profile=~/.config/firejail/joplin.profile --appimage ~/Downloads/appImages/Joplin-2.7.15.AppImage

Which works.
But then doing this:
firejail --build=~/.config/firejail/joplin.profile --appimage ~/Downloads/appImages/Joplin-2.7.15.AppImage
Gives an error:
Error: cannot open profile file.

P.S.: For some unknown reason only absolute path for appimages is working. Interestingly below mentioned command didn't even read my globals.local

firejail --profile=joplin.profile --appimage ~/Downloads/appImages/Joplin-2.7.15.AppImage
Reading profile joplin.profile
Reading profile /etc/firejail/disable-common.inc
Reading profile /etc/firejail/disable-programs.inc
Reading profile /etc/firejail/whitelist-usr-share-common.inc
Reading profile /etc/firejail/whitelist-var-common.inc
Parent pid 5732, child pid 5734

** Warning: dropping all Linux capabilities and setting NO_NEW_PRIVS prctl **

Mounting appimage type 2
Warning: NVIDIA card detected, nogroups command ignored
Warning: NVIDIA card detected, nogroups command ignored
Warning: NVIDIA card detected, nogroups command ignored
Warning: skipping none for private /etc
Private /etc installed in 0.08 ms
Private /usr/etc installed in 0.00 ms
Warning: /sbin directory link was not blacklisted
Warning: /usr/sbin directory link was not blacklisted
Blacklist violations are logged to syslog
Warning: NVIDIA card detected, nogroups command ignored
Warning: cleaning all supplementary groups
Child process initialized in 91.08 ms
Check failed: sys_chroot("/proc/self/fdinfo/") == 0

Parent is shutting down, bye...
AppImage detached

@glitsj16
Copy link
Collaborator

My intend wasn't to post an issue but to illustrate how you can enhance upon the profiling aspect of firejail.

Fair enough, no problem.

They don't work as intended.

Noticing your examples are using AppImages, it might be worthwhile to check your Firejail version. The build options got appimage support only recently via #4878. Not sure OTOH if that made it into the latest release. To make sure you could give building from git a try.

Regarding your Joplin examples. You can create your own custom profiles in ~/.config/firejail, no problem doing so. But the --build=foo option is not intended to use an already existing profile AFAIK. At least that doesn't make much sense, as its purpose is to create one from scratch. So I'd go for something like;
$ firejail --build=~/Downloads/build-joplin.profile --appimage ~/Downloads/appImages/Joplin-2.7.15.AppImage
and then start comparing the output of ~/Downloads/build-joplin.profile with what you already have in ~/.config/firejail/joplin.profile to see if/what you can add to harden it according to your needs. Documentation might be unclear on this I guess.

@romelsalwi
Copy link
Author

romelsalwi commented Mar 28, 2022

I'm using firejail version 0.9.68
https://archlinux.org/packages/community/x86_64/firejail/

I tried out your step:

firejail --build=/home/***/Downloads/appImages/joplin.profile --appimage ~/Downloads/appImages/Joplin-2.7.15.AppImage 
Check failed: sys_chroot("/proc/self/fdinfo/") == 0

Then ran the appimage with the created profile:


firejail --profile=/home/***/Downloads/appImages/joplin.profile --appimage ~/Downloads/appImages/Joplin-2.7.15.AppImage 
Reading profile /home/l3m0r/Downloads/appImages/joplin.profile
Reading profile /etc/firejail/disable-common.inc
Reading profile /etc/firejail/disable-programs.inc
Reading profile /etc/firejail/whitelist-usr-share-common.inc
Reading profile /etc/firejail/whitelist-var-common.inc
Parent pid 4705, child pid 4708

** Warning: dropping all Linux capabilities and setting NO_NEW_PRIVS prctl **

Mounting appimage type 2
Warning: NVIDIA card detected, nogroups command ignored
Warning: NVIDIA card detected, nogroups command ignored
Warning: NVIDIA card detected, nogroups command ignored
Warning: skipping none for private /etc
Private /etc installed in 0.13 ms
Private /usr/etc installed in 0.00 ms
Warning: /sbin directory link was not blacklisted
Warning: /usr/sbin directory link was not blacklisted
Blacklist violations are logged to syslog
Warning: NVIDIA card detected, nogroups command ignored
Warning: cleaning all supplementary groups
Child process initialized in 119.39 ms
Check failed: sys_chroot("/proc/self/fdinfo/") == 0

Parent is shutting down, bye...
AppImage detached

@romelsalwi
Copy link
Author

I also tried using the firetools configuration wizard. No luck

@romelsalwi
Copy link
Author

romelsalwi commented Mar 28, 2022

Hmm, KeePassXC-2.7.0-x86_64.AppImage worked! But the profile created is barely restricted.

EDIT:
After few mods, and loading with the generated profile, the application gives an error
Access error for config file /home/*/.config/keepassxc/keepassxc.ini

### Home Directory Whitelisting ###
### If something goes wrong, this section is the first one to comment out.
### Instead, you'll have to relay on the basic blacklisting above.
noblacklist ${HOME}/.config/keepassxc
whitelist ${HOME}/.mozilla/native-messaging-hosts
whitelist ${HOME}/.config/chromium/NativeMessagingHosts
whitelist ${HOME}/.config/keepassxc
whitelist ${HOME}/.cache/keepassxc

@rusty-snake
Copy link
Collaborator

--build

TBH It is out of dated and hasn't seen much development for a long time. IMHO we should just remove it from firejail (the binary) and develop an external tool for profile building (in a language which has friendlier support for string parsing & co like python.

@rusty-snake
Copy link
Collaborator

What I mean is something like

firejail-profile-builder.py
#!/usr/bin/python3
# SPDX-License-Identifier: ISC

# Copyright © 2022 rusty-snake
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.

import argparse
import os.path
import pathlib
import re
import subprocess
import sys
import tempfile


def get_cli_args(argv: list[str]) -> dict[str, str]:
    parser = argparse.ArgumentParser()
    parser.add_argument("-o", "--output", required=True)
    parser.add_argument("program")
    parser.add_argument("arguments", nargs=argparse.REMAINDER)
    return vars(parser.parse_args(argv[1:]))


def run_program(program: str, arguments: list[str]) -> list[str]:
    with tempfile.NamedTemporaryFile() as tmpf:
        subprocess.run(
            [
                "firejail",
                "--quiet",
                "--noprofile",
                "--private",
                "strace",
                "-e",
                "%file",
                "--quiet=all",
                "--follow-forks",
                "--output",
                tmpf.name,
                "--",
                program,
                *arguments,
            ],
            check=True,
        )
        return list(tmpf.read().decode().splitlines())


def parse_strace_output(strace_output: list[str]) -> dict[str, set[str]]:
    paths = {
        "open": set(),
        "stat": set(),
        "exec": set(),
    }
    for line in strace_output:
        parsed_line = re.match(
            r"\d+\s+(?P<syscall>\w+)\((?P<args>.*)\)", line
        ).groupdict()
        syscall = parsed_line["syscall"]
        args = parsed_line["args"].split(",")
        if syscall == "open":
            paths["open"].add(args[0].strip(' "'))
        elif syscall == "openat":
            paths["open"].add(args[1].strip(' "'))
        elif syscall == "access":
            paths["stat"].add(args[0].strip(' "'))
        elif syscall == "stat":
            paths["stat"].add(args[0].strip(' "'))
        elif syscall == "newfstatat":
            paths["stat"].add(args[1].strip(' "'))
        elif syscall == "execve":
            paths["exec"].add(args[0].strip(' "'))
        else:
            print(
                f"firejail-profile-builder.py: Not Implemented: {syscall=}",
                file=sys.stderr,
            )
    return paths


def build_profile(paths: dict[str, set[str]]) -> str:
    whitelist = []
    private_bin = []
    ignore_noexec_home = False
    for path in paths["open"]:
        if path.startswith(str(pathlib.Path.home())):
            whitelist.append(path.replace(str(pathlib.Path.home()), "${HOME}"))
        elif (
            path.startswith("/bin")
            or path.startswith("/sbin")
            or path.startswith("/usr/bin")
            or path.startswith("/usr/sbin")
        ):
            private_bin.append(os.path.basename(path))
    for path in paths["exec"]:
        if path.startswith(str(pathlib.Path.home())):
            ignore_noexec_home = True
        elif (
            path.startswith("/bin")
            or path.startswith("/sbin")
            or path.startswith("/usr/bin")
            or path.startswith("/usr/sbin")
        ):
            private_bin.append(os.path.basename(path))

    return f"""\
{"ignore noexec ${HOME}" if ignore_noexec_home else "# Uncomment to allow executing programs in ${HOME}.<br>#ignore noexec ${HOME}"}

include disable-common.inc
include disable-exec.inc
include disable-programs.inc

{"<br>".join(f"whitelist {path}" for path in whitelist)}
include whitelist-common.inc

private-bin {",".join(private_bin)}
""".replace(
        "<br>", "\n"
    )


def main(argv: list[str]) -> int:
    args = get_cli_args(argv)
    strace_output = run_program(args["program"], args["arguments"])
    paths = parse_strace_output(strace_output)
    with open(args["output"], "w") as output:
        output.write(build_profile(paths))
    return 0


if __name__ == "__main__":
    try:
        sys.exit(main(sys.argv))
    except KeyboardInterrupt:
        pass

@romelsalwi
Copy link
Author

Nice 👍
Is it possible to ask the user before stracer updates the profile?

@rusty-snake
Copy link
Collaborator

Python is really much much better than C for tasks like this.

https://github.com/rusty-snake/firejail-profile-builder

@romelsalwi

This comment was marked as off-topic.

@romelsalwi

This comment was marked as off-topic.

@rusty-snake
Copy link
Collaborator

I tried out the script. It didn't created the desired effect.

It's still in development.

It started out at ease while building but when used as a profile it didn't worked out.

It will never get the quality of a handwritten profile.

turtlapp

is an electron app, this will need special handling. We will need to detect this.

@rusty-snake
Copy link
Collaborator

If you did not disable unprivileged userns, it may work now with electron programs.

@romelsalwi
Copy link
Author

romelsalwi commented Apr 8, 2022

Here's something I found relevant:
https://gitlab.com/apparmor/apparmor/-/wikis/AppArmor_Core_Policy_Reference
Apparmor uses a profile language, and with the help of auditd, you can trace what the confined app is trying to access.

The interesting and the interactive part I found was globbing. You can glob the directory and tell apparmor how much access the application has, and apply it to the profile using logprof.

By far I have tried it on Firefox and few other frequently used applications and the process when implemented correctly can provide finer result

I followed these pages:
https://wiki.archlinux.org/title/AppArmor#Configuration
https://wiki.archlinux.org/title/Audit_framework#Adding_rules

During the profile generating process genprof, apparmor is going through the audit reports. It notifies (through aa-notify) what files were accessed, and when you are finished, it creates a profile. It is not compulsive for the application to run as expected and that is why it allows you to run the application in complain mode so that you can refine those edges. When convinced, you can make the rules strict by enabling enforce mode.

@kmk3 kmk3 added the enhancement New feature request label Aug 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature request
Projects
None yet
Development

No branches or pull requests

4 participants