$SNAP_USER_COMMON empty when getent cannot be found in $PATH

Bug #2090938 reported by Alex Lowe
16
This bug affects 3 people
Affects Status Importance Assigned to Milestone
snapd
New
Undecided
Unassigned

Bug Description

This seems to be a regression when changing how user data directories are built, as it works on Jammy (snapd 2.63+22.04ubuntu0.1) but not on noble (snapd 2.66.1).

If getent can't be found in $PATH when running a snap, snapd will output a warning:

2024/12/03 15:17:52.136805 cmd_run.go:1276: WARNING: cannot create user data directory: cannot get the current user: getent could not be executed: exec: "getent": executable file not found in $PATH

and then set the SNAP_USER_COMMON environment variable to be empty (or maybe not set it?)

This means that an app that depends on $SNAP_USER_COMMON (e.g. charmcraft: https://github.com/canonical/charmcraft/blob/c13f20ae65b6a4d3657dcdd30b2f606ccae86163/snap/snapcraft.yaml#L63) will get an empty directory for that and may try to write to /.

To reproduce:

sudo snap install --classic charmcraft --channel=2.x/stable
echo '$SNAP/bin/python -c "from charmcraft import env; print(env.get_host_shared_cache_path())"' | PATH=/snap/bin /usr/bin/snap run --shell charmcraft

Expected behaviour occurs on jammy: https://github.com/canonical/craft-platforms/actions/runs/12142670371/job/33857713122

Actual behaviour on noble: https://github.com/canonical/craft-platforms/actions/runs/12142670371/job/33857714001

Alex Lowe (lengau)
description: updated
Alex Lowe (lengau)
summary: - $SNAP_USER_COMMON incorrect when getent cannot be found in $PATH
+ $SNAP_USER_COMMON empty when getent cannot be found in $PATH
Revision history for this message
Alex Lowe (lengau) wrote :

This does not appear to break on 25.04 with snapd 2.66.1+25.04:

$ echo '$SNAP/bin/python -c "from charmcraft import env; print(env.get_host_shared_cache_path())"' | PATH=/snap/bin /usr/bin/snap run --shell charmcraft_2
/home/lengau/snap/charmcraft_2/common/cache/charmcraft

Revision history for this message
Alex Lowe (lengau) wrote (last edit ):

Confirmed in a fresh plucky LXD container (started with oracular, ran `do-release-upgrade -d`) that this issue does not occur, but that it does in oracular. In both cases, the snapd revision is 23258.

$ lxc shell plucky
root@plucky:~# echo 'env|grep SNAP_USER' | PATH=/snap/bin /usr/bin/snap run --shell charmcraft
error: snap "charmcraft" is not installed
root@plucky:~# snap install --classic charmcraft
2024-12-03T16:54:52Z INFO Waiting for automatic snapd restart...
charmcraft 3.2.2 from Canonical✓ installed
root@plucky:~# echo 'env|grep SNAP_USER' | PATH=/snap/bin /usr/bin/snap run --shell charmcraft
SNAP_USER_COMMON=/root/snap/charmcraft/common
SNAP_USER_DATA=/root/snap/charmcraft/5303
root@plucky:~# snap version
snap 2.66.1+25.04
snapd 2.66.1+25.04
series 16
ubuntu 25.04
kernel 6.11.0-9-generic

$ lxc shell oracular
root@oracular:~# echo 'env|grep SNAP_USER' | PATH=/snap/bin /usr/bin/snap run --shell charmcraft
2024/12/03 16:56:00.416539 cmd_run.go:1276: WARNING: cannot create user data directory: cannot get the current user: getent could not be executed: exec: "getent": executable file not found in $PATH
2024/12/03 16:56:00.416623 cmd_run.go:1281: WARNING: cannot copy user Xauthority file: cannot get the current user: getentcould not be executed: exec: "getent": executable file not found in $PATH
root@oracular:~# snap version
snap 2.66.1
snapd 2.66.1
series 16
ubuntu 24.10
kernel 6.11.0-9-generic

Revision history for this message
Ernest Lotter (ernestl) wrote :

[1]
The observed behaviour happens anywhere where the snapd snap >= 2.66.1 runs.
As per above examples, the version "snap 2.66.1+25.04" indicates snapd deb is active.

Another way to confirm this is: `SNAPD_DEBUG=1 snap version`
output: `logger.go:99: DEBUG: snap (at "/snap/snapd/current") is older ("2.66.1") than distribution package ("2.66.1+25.04")`

[2]
This change in behavior was introduced into snapd 2.66.1 here: https://github.com/canonical/snapd/pull/13776
The snapd snap is build with tag: `snapdusergo` which picks `osutil/user/user_from_snap.go` (the getent based user lookup) instead of `osutil/user/user.go` (the LDAP based user lookup).

[3]
Execution path for error cmd_run.go:1276: WARNING: cannot create user data directory: cannot get the current user: getent could not be executed: exec: "getent": executable file not found in $PATH":

https://github.com/canonical/snapd/blob/master/cmd/snap/cmd_run.go#L1275 ->
https://github.com/canonical/snapd/blob/master/cmd/snap/cmd_run.go#L394 ->
https://github.com/canonical/snapd/blob/master/osutil/user/user_from_snap.go#L42 ->
https://github.com/canonical/snapd/blob/master/osutil/user/getent.go#L179 ->
https://github.com/canonical/snapd/blob/master/osutil/user/getent.go#L61 ->
https://github.com/canonical/snapd/blob/master/osutil/user/getent.go#L33 ->
https://cs.opensource.google/go/go/+/master:src/os/exec/lp_unix.go;l=81?q=%22executable%20file%20not%20found%20in%22&ss=go%2Fgo

This happens in the snap run context, BEFORE snap calls snap-confine, and before any interaction with env passed on to snap confine. https://github.com/canonical/snapd/blob/master/cmd/snap/cmd_run.go#L1354

Also, snap-confine, itself will reset $PATH to ensure it is what it should be, and not externally manipulated: https://github.com/canonical/snapd/blob/master/cmd/snap-confine/snap-confine.c#L887

[4]
CONCLUSION:
The problem is that the $PATH provided to `snap run`, `PATH=/snap/bin`, is used by
https://github.com/canonical/snapd/blob/master/cmd/snap/cmd_run.go#L1275 and getent cannot be found by exec.Command() that looks at $PATH

Removing `PATH=` works always:

echo 'env|grep SNAP_USER' | /usr/bin/snap run --shell charmcraft

echo '$SNAP/bin/python -c "from charmcraft import env; print(env.get_host_shared_cache_path())"' | /usr/bin/snap run --shell charmcraft

Why specify `PATH=/snap/bin` ??

Revision history for this message
Alex Lowe (lengau) wrote :

Thanks for the great additional details Ernest!

[1]: Thanks for the explanation - switching to the beta channel (2.67) let me reproduce this on plucky.
[2]: That makes sense, though the loss of SNAP_USER_COMMON is surprising to users of snapd
[3]: Perhaps that PATH could be used as a fallback if getent can't be found on the provided PATH?
[4]:
In answer to your question about why specify PATH, the example of `PATH=/snap/bin` is just to illustrate a PATH that won't contain getent. The original place where this was found (https://github.com/canonical/operator-workflows/issues/496) was resetting the environment and leaving PATH unset, which results in no SNAP_USER_COMMON environment variable.

I'm uncertain about correctness guarantees of SNAP_USER_COMMON before the PR #13776, but in snapd 2.63 at least, SNAP_USER_COMMON is set to a user-writable directory. After this PR, SNAP_USER_COMMON is unset, causing unexpected behaviour with the given apps definition in charmcraft's snap.yaml (irrelevant parts excluded):

apps:
  charmcraft:
    command: bin/python3 -u $SNAP/bin/charmcraft
    environment:
      XDG_CACHE_HOME: $SNAP_USER_COMMON/cache

Here Charmcraft is detecting the cache directory in a snap-agnostic manner (specifically using the platformdirs package for Python, which looks up the XDG_CACHE_HOME environment variable if it is set). Because snap doesn't set SNAP_USER_COMMON, when setting up the app this configuration also causes it to set XDG_CACHE_HOME to /cache before launching the application.

The documentation (https://snapcraft.io/docs/environment-variables#heading--snap-user-common) implies that SNAP_USER_COMMON will always be available. Even changing charmcraft's snap.yaml as follows:

apps:
  charmcraft:
    command: bin/python3 -u $SNAP/bin/charmcraft
    environment:
      XDG_CACHE_HOME: ${SNAP_USER_COMMON:-$HOME/snap/charmcraft/common}/cache
      BOOP: ${BOOPITY_BOOP:-default}

results the same XDG_CACHE_HOME and an empty BOOP environment variable, so we also don't (AFAIK) have a way to have XDG_CACHE_HOME fall back to a safe default.

A simpler reproducer (though with a strictly confined snap rather than classic - not that I think that's relevant to this bug) is:

echo 'echo $SNAP_USER_COMMON' | PATH= /usr/bin/snap run --shell hello

Revision history for this message
Ernest Lotter (ernestl) wrote :

[4] Understood thanks.

We are working/thinking on solution avoid a bad PATH affecting snap run in any way, should be pretty simple.

What I further take from your point, is the we should also reconsider if it is correct, in principle, to only log a warning if user directories cannot be created as we currently do: https://github.com/canonical/snapd/blob/master/cmd/snap/cmd_run.go#L1276

Revision history for this message
Ernest Lotter (ernestl) wrote :

Please confirm that this issue can be worked around by avoiding empty $PATH in the test where the issue was original discovered?

To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.