canonical / snapcraft

Package, distribute, and update any app for Linux and IoT.
https://snapcraft.io
GNU General Public License v3.0
1.18k stars 444 forks source link

bash gets built with a /bin/bash that is missing the executable bit #5114

Open basak opened 1 week ago

basak commented 1 week ago

Bug Description

If I stage the bash and quilt packages in a part in a classic snap, then the snap gets built with a bin/bash file that is not executable. Subsequent attempts to use it fail.

To Reproduce

On a fresh Noble VM:

sudo snap install --classic snapcraft
# installs 8.4.3 (12823)
mkdir -p demo/snap
cat > demo/snap/snapcraft.yaml <<EOT
name: demo
version: 1
summary: demo
description: demo
confinement: classic
grade: devel
base: core24

apps:
  demo:
    command: usr/bin/bash

parts:
  demo:
    build-attributes: [enable-patchelf]
    plugin: nil
    stage-packages: [bash, quilt]
EOT
cd demo
snapcraft build --destructive-mode

Expected result: parts/demo/install/bin/bash is executable, and this passes through to the final snap (if you continue you should also see it in prime/bin/bash as executable, etc).

Actual result: parts/demo/install/bin/bash is not executable and this passes through to the final snap. Then scripts that try to use it fail.

Environment

Ubuntu 24.04 cloud image, snapcraft in destructive mode. Snapcraft snap 8.4.3 (12823).

snapcraft.yaml

name: demo
version: 1
summary: demo
description: demo
confinement: classic
grade: devel
base: core24

apps:
  demo:
    command: usr/bin/bash

parts:
  demo:
    build-attributes: [enable-patchelf]
    plugin: nil
    stage-packages: [bash, quilt]

Relevant log output

2024-10-13 13:12:26.879 Building demo                                                                                                                                                                             
2024-10-13 13:12:26.893 execute action demo:Action(part_name='demo', step=Step.BUILD, action_type=ActionType.RUN, reason=None, project_vars=None, properties=ActionProperties(changed_files=None, changed_dirs=None))                                                                                                                                                                                                                
2024-10-13 13:12:26.894 load state file: /home/ubuntu/demo/parts/demo/state/pull                                                                                                                                  
2024-10-13 13:12:28.158 fix artifacts: unpack_dir='/home/ubuntu/demo/parts/demo/install'                                                                                                                          
2024-10-13 13:12:28.175 fix symlink: path='/home/ubuntu/demo/parts/demo/install/usr/share/quilt/compat/bash', unpack_dir='/home/ubuntu/demo/parts/demo/install', root='/home/ubuntu/demo/parts/demo/install/usr/share/quilt/compat'                                                                                                                                                                                                  
2024-10-13 13:12:28.207 fix symlink: target='/home/ubuntu/demo/parts/demo/install/bin/bash'                                                                                                                       
2024-10-13 13:12:28.207 Copying needed target link from the system: /usr/bin/bash                                                                                                                                 
2024-10-13 13:12:30.039 remove directory /home/ubuntu/demo/parts/demo/build                                                                                                                                       
2024-10-13 13:12:30.041 Executing PosixPath('/home/ubuntu/demo/parts/demo/run/build.sh')

Additional context

The trace output suggests that snapcraft is doing something because usr/share/quilt/compat/bash is a symlink. If I drop quilt from stage-packages, then bin/bash isn't created.

I found this when trying to move the git-ubuntu snap to core24. That's complex enough so I came up with a minimal reproducer.

Workaround: I did this:

        override-stage: |
            craftctl default
            chmod 755 bin/bash
lengau commented 6 days ago

This seems to be happening here: https://github.com/canonical/craft-parts/blob/5b9dffb363c90e77f6e4f448852688251ad5bd70/craft_parts/packages/normalize.py#L148-L157

Please note that since you're building a classic snap, the symlink in usr/share/quilt/compat/bash may be incorrect. Do you expect this to go to the system's bash, or the bash staged inside this snap? If the latter, you'll need to rewrite that symlink (probably in an override-build script) to point to ../../../bin/bash rather than /bin/bash - otherwise it'll execute the system's bash.

@mr-cal @cmatsuoka Should craft-parts be rewriting absolute symlinks like this when staging a package?

basak commented 6 days ago

Do you expect this to go to the system's bash, or the bash staged inside this snap?

In quilt's case, I'd like for it to be entirely contained within the classic snap, and not touch the host system except to act on the source tree and patch series that it is given that will be on the host system. That suggests that the symlinks should be relative.

Of course it's tricky in the case of a classic snap where these sorts of things tend to be leaky. I'm not sure the symlinks are actually used. If they are, then that's probably wrong and I can close that leak, but there are probably others. I try to ensure that there's good test coverage (I know quilt is exercised), and then act when I have specific test failures. But sure, I can tweak this in override-build to make them relative if snapcraft can't do that for me in any other more abstract way.

The reason I ran into this was because in some other places I was using $SNAP/bin/bash and because I happened to stage-packages: [quilt], switching from core20 to core24 regressed that location. I know behaviour has changed since usrmerge. I've tried to eliminate use of that path now, favouring $SNAP/usr/bin/bash instead for unrelated reasons.