Noexec /tmp-folder

Hi, in the fiduswriter-snap I maintain, I have included a Java-program. The Java-program executes things in the /tmp-folder as many Java programs do. I have now received error reports that the snap doesn’t run on some Ubuntu installation with hardened security setup due to this. I realize I can make Java use another directory as tmp-folder (-Djava.io.tmpdir=/tmp/folder/with/exec/permissions), but I wonder what the best practice is for this case:

  • Should I create another folder that acts as a tmp folder for this Java-program or is there some other recommendation?
  • Where should such a folder be?
  • What do I do concerning cleanup of a tmp-folder I create manually? (making sure it’s not filling up with junk over time)

Use ${XDG_RUNTIME_DIR}.

  1. It’s guaranteed to exist, snap will ensure you have your own version of this folder with permissions sorted out to use it
  2. It’s a system defined folder with characteristics like /tmp, I.E., you don’t manage cleanup, the OS does.
  3. It’s scoped per user, so there’s no leakage of files across user boundaries.
  4. It happens to have the same folder structure in the runtime environment as the host environment, which mitigates very specific edge cases that aren’t worth mentioning individually but do exist from time to time. (One example would be e.g Task Tray icons).

@BrandyN11 Thanks, that’s also what I was thinking. I was wondering about the specifics though - which folder and where and what mechanism to use to ensure the folder exists, etc. .

@James-Carroll Interesting. Unfortunately, the Java program is a system daemon, and reading up on XDG_RUNTIME_DIR it is only created when there is a session for a given user and it will be deleted when that user logs out.

In a system context I’m unsure if there’s an appropriate folder but I’m wondering if you might be able to create your own?

Using layouts I’d imagine you might be able to create a layout e.g:

layout:
  /var/mysnap/tmp:
    type: tmpfs

You could then point your snap to use this /var/mysnap/tmp which would be cleared up by the system (at some points, unfortunately I’m not sure on the exact triggers).

Since it’s not created from /tmp itself and becomes a brand new mount, I’m hoping it might not have noexec set, but you’d have to test this unfortunately as I’ve never tried or seen similar.

1 Like

Thanks @James-Carroll . Based on your previous answer I did some searching and found that another option seems to be:

It doesn’t say where exactly the mkdir should be placed - but it sounded like a solution as it doesn’t depend on a user folder.

I will try out both your suggestion and this.

snap daemons run under UID 0 by default and get an XDG_RUNTIME_DIR assigned automatically:

$ sudo snap run --shell opencv-html-demo.daemon -c 'env | grep ^XDG_RUNTIME_DIR'
XDG_RUNTIME_DIR=/run/user/0/snap.opencv-html-demo
$

there is no need to jump through hoops here, just use that variable as many other snaps do too …

@ogra OK, so the remarks here about using it outside of a session are outdated then?

I would strongly recommend against trying to use XDG_RUNTIME_DIR outside of a user session (even root’s /run/user/0 folder). It will likely appear to work fine until it suddenly breaks.

As an example, the following chain of events will cause you pain:

  1. System service running outside of a user session puts something in /run/user/0.
  2. A user logs in as root (e.g. via ssh).
  3. The user logs.
  4. The contents of the /run/user/0 directory are deleted, because the last login session for that user has exited.
  5. The system service finds that it’s files have gone and malfunctions.
1 Like

well, for a systemd service i’d actually expect systemd to take care of its internal session information and TBH i know a lot of people use it that way on UbuntuCore setups where nothing gets deleted when someone logs in as root (well … uses “sudo -s” or “sudo -i” since there is no root account you can log in to)

I see. tricky then.

So how I but I run this (a python script that is starting the languagetool daemon - a Java program) and creates a folder inside of /run/snap.$SNAP_INSTANCE_NAME/ for languagetool if it doesn’t exist already:

#!/usr/bin/env python3
import os
import sys
from subprocess import call
from pathlib import Path

SNAP = os.environ.get("SNAP")
SNAP_DATA = os.environ.get("SNAP_DATA")
sys.path.append(SNAP_DATA)
JAVA_BIN = os.environ.get("JAVA_BIN")
SNAP_INSTANCE_NAME = os.environ.get("SNAP_INSTANCE_NAME")
RUN_DIR = f"/run/snap.{SNAP_INSTANCE_NAME}/lt/"

if __name__ == "__main__":
    Path(RUN_DIR).mkdir(parents=True, exist_ok=True)
    call(
        [
            JAVA_BIN,
            "-Djava.io.tmpdir={}".format(RUN_DIR),
            "-cp",
            "{}/lt/languagetool-server.jar".format(SNAP),
            "org.languagetool.server.HTTPServer",
        ]
    )

@jamesh @ogra if you can come to a common conclusion on what is best practice here, that would be of interest to me. I’d really prefer to follow a default way of doing this - and if there isn’t one, for one to be defined somewhere. The snap is supposed to be able to run in all or almost all the different ways people run snaps. Most will just be default Ubuntu, but some are running it on hardened Ubuntu or even a different linux distribution that supports snapd.

Personally I think given the potential caveats, the layout still makes sense here.

There’s a warning saying not to overuse them for performance reasons, but a single mount for an important reason makes perfect sense, especially when being a system daemon the penalty is a thousandth of a second once per boot.

The folder is guaranteed to exist because you’d be explicitly creating it and it’s guaranteed to be temporary too.

james surely has a valid point.

i’m operating on the assumption that this would be a bug when systemd services can not own session bits and that i have not seen any breakage as described by james doesnt mean it doesnt exist … so perhaps the layout is the best option here …

i’m not sure you can actually call mkdir /run/snap.${SNAP_INSTANCE_NAME} like you do above FWIW, since that would mean your snap needs actual write permissions to the toplevel /run

I see. ok, yes, that makes sense. I wonder what the was meant by this comment then:

for /run-style data you can use /run/snap.$SNAP_INSTANCE_NAME/ (that will need to be created with a suitable mkdir before it can be used)

@ogra I see you also commented in that issue. Maybe you remember?

As for the solution @James-Carroll presents: The /tmp-folder within a snap is a subfolder of the system’s /tmp-folder, so it inherits the noexec rule of the tmp-folder. Can I be certain that creating a tmpfs using a layout will not create the same problem?Or do those tmpfs folders live somewhere completely different?

There were some people who ran into these exact problems on Ubuntu Core, where they’d enabled direct root login. Their system worked until they ssh’d in to check the system, and everything broke when they logged out.

By a “session” I mean something that will show up with loginctl list-sessions. Running sudo does not create a session for the target user. If the lifetime of your program does not match the lifetime of a session for the user, then using XDG_RUNTIME_DIR is essentially setting a land mine for your future self.

hmm, i wonder how that is that possible with a readonly /etc/shadow that locks the root account … you’d have to bind mount something over it i suppose … (note that 99.9% of our commercial UbuntuCore customers actually follow our advise and do never create a user account and disable ssh on their production devices (to then use an agent with the REST api to manage the system), i’m probably missing the community side here where people manually tinker with it)

Presumably they put a key in /root/.ssh/authorized_keys.

1 Like

ah, i took “direct login” as console login … sorry

Can we have a general solution that works across setups? I cannot know what kind of setups the users of my snap have. Having a standardized snap that works across setups is the point with snaps, right? How can the folder at /run/snap.$SNAP_INSTANCE_NAME/ be created without running into access issues? Alternatively, is the using a custom tmpfs safe or will it inherit the same noexec option that the /tmp folder has?