Exemple #1
0
    def test_patch_does_nothing_if_no_interpreter(self):
        elf_file = self.fake_elf['fake_elf-static']
        # The base_path does not matter here as there are not files to
        # be crawled for.
        elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld',
                                  root_path='/fake')
        elf_patcher.patch(elf_file=elf_file)

        self.assertFalse(self.check_call_mock.called)
Exemple #2
0
    def test_patch(self, check_call_mock):
        elf_file = elf.ElfFile(path='/fake-elf', is_executable=True)
        elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld')
        elf_patcher.patch(elf_file=elf_file)

        check_call_mock.assert_called_once_with([
            self.expected_patchelf, '--set-interpreter', '/lib/fake-ld',
            '/fake-elf'
        ])
Exemple #3
0
    def test_patch_fails_raises_patcherror_exception(self):
        elf_file = self.fake_elf["fake_elf-bad-patchelf"]
        # The base_path does not matter here as there are not files to
        # be crawled for.
        elf_patcher = elf.Patcher(dynamic_linker="/lib/fake-ld", root_path="/fake")

        self.assertRaises(
            errors.PatcherGenericError, elf_patcher.patch, elf_file=elf_file
        )
Exemple #4
0
    def prime(self, force=False) -> None:
        self.makedirs()
        self.notify_part_progress('Priming')
        snap_files, snap_dirs = self.migratable_fileset_for('prime')
        _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir)

        elf_files = elf.get_elf_files(self.primedir, snap_files)
        all_dependencies = set()
        # TODO: base snap support
        core_path = common.get_core_path()

        for elf_file in elf_files:
            all_dependencies.update(
                elf_file.load_dependencies(root_path=self.primedir,
                                           core_base_path=core_path))

        # Split the necessary dependencies into their corresponding location.
        # We'll both migrate and track the system dependencies, but we'll only
        # track the part and staged dependencies, since they should have
        # already been primed by other means, and migrating them again could
        # potentially override the `stage` or `snap` filtering.
        (in_part, staged, primed, system) = _split_dependencies(
            all_dependencies, self.installdir, self.stagedir, self.primedir)

        part_dependency_paths = {os.path.dirname(d) for d in in_part}
        staged_dependency_paths = {os.path.dirname(d) for d in staged}

        dependency_paths = part_dependency_paths | staged_dependency_paths

        if not self._build_attributes.no_system_libraries():
            system_dependency_paths = {os.path.dirname(d) for d in system}
            dependency_paths.update(system_dependency_paths)

            if system:
                # Lots of dependencies are linked with a symlink, so we need to
                # make sure we follow those symlinks when we migrate the
                # dependencies.
                _migrate_files(system, system_dependency_paths, '/',
                               self.primedir, follow_symlinks=True)
                formatted_system = '\n'.join(sorted(system))
                logger.warning(
                    'Files from the build host were migrated into the snap to '
                    'satisfy dependencies that would otherwise not be met. '
                    'This feature will be removed in a future release. If '
                    'these libraries are needed in the final snap, ensure '
                    'that the following are either satisfied by a '
                    'stage-packages entry or through a part:\n{}'.format(
                        formatted_system))

        if self._confinement == 'classic':
            dynamic_linker = self._project_options.get_core_dynamic_linker()
            elf_patcher = elf.Patcher(dynamic_linker=dynamic_linker,
                                      root_path=self.primedir)
            for elf_file in elf_files:
                elf_patcher.patch(elf_file=elf_file)

        self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
    def test_patch_fails_raises_patcherror_exception(self):
        elf_file = elf.ElfFile(path='/fake-elf', magic=self.stub_magic)
        # The base_path does not matter here as there are not files to
        # be crawled for.
        elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld',
                                  root_path='/fake')

        self.assertRaises(errors.PatcherError,
                          elf_patcher.patch,
                          elf_file=elf_file)
Exemple #6
0
    def test_patch_fails_raises_patcherror_exception(self, check_call_mock):
        stub_magic = ('ELF 64-bit LSB executable, x86-64, version 1 (SYSV), '
                      'dynamically linked, interpreter '
                      '/lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32')
        elf_file = elf.ElfFile(path='/fake-elf', magic=stub_magic)
        elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld')

        self.assertRaises(errors.PatcherError,
                          elf_patcher.patch,
                          elf_file=elf_file)
Exemple #7
0
    def test_patch_fails_raises_patcherror_exception(self):
        elf_file = self.fake_elf['fake_elf-2.23']
        # The base_path does not matter here as there are not files to
        # be crawled for.
        elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld',
                                  root_path='/fake')

        self.assertRaises(errors.PatcherError,
                          elf_patcher.patch,
                          elf_file=elf_file)
Exemple #8
0
def handle_glibc_mismatch(*,
                          elf_files: FrozenSet[elf.ElfFile],
                          root_path: str,
                          core_base_path: str,
                          snap_base_path: str,
                          preferred_patchelf_path=None,
                          soname_cache: elf.SonameCache = None) -> None:
    """Copy over libc6 libraries from the host and patch necessary elf files.

    If no newer glibc version is detected in elf_files, this function returns.
    The dynamic linker and related libraries to libc6 are expected to be found
    in root_path.

    :param snapcraft.internal.elf.ElfFile elf_files:
        set of candidate elf files to patch if a newer libc6 is required.
    :param str root_path: the root path of a snap tree.
    :param str core_base_path: the path to the base snap.
    :param str snap_base_path: absolute path to the snap once installed to
                               setup proper rpaths.
    :param str preferred_patchelf_path: patch the necessary elf_files with
                                        this patchelf.
    """
    if soname_cache is None:
        soname_cache = elf.SonameCache()

    # We assume the current system will satisfy the GLIBC requirement,
    # get the current libc6 libraries (which includes the linker)
    libc6_libraries_list = repo.Repo.get_package_libraries('libc6')

    # For security reasons, we do not want to automatically pull in
    # libraries but expect them to be consciously brought in by stage-packages
    # instead.
    libc6_libraries_paths = [
        os.path.join(root_path, l[1:]) for l in libc6_libraries_list
    ]

    dynamic_linker = _get_dynamic_linker(libc6_libraries_paths)

    # Get the path to the "would be" dynamic linker when this snap is
    # installed. Strip the root_path from the retrieved dynamic_linker
    # variables + the leading `/` so that os.path.join can perform the
    # proper join with snap_base_path.
    dynamic_linker_path = os.path.join(snap_base_path,
                                       dynamic_linker[len(root_path) + 1:])

    elf_patcher = elf.Patcher(dynamic_linker=dynamic_linker_path,
                              root_path=root_path,
                              preferred_patchelf_path=preferred_patchelf_path)
    for elf_file in elf_files:
        # Search for dependencies again now that the new libc6 is
        # migrated.
        elf_file.load_dependencies(root_path=root_path,
                                   core_base_path=core_base_path,
                                   soname_cache=soname_cache)
        elf_patcher.patch(elf_file=elf_file)
Exemple #9
0
    def test_patch_does_nothing_if_no_interpreter(self, check_call_mock):
        stub_magic = ('ELF 64-bit LSB shared object, x86-64, '
                      'version 1 (SYSV), dynamically linked')
        elf_file = elf.ElfFile(path='/fake-elf', magic=stub_magic)
        # The base_path does not matter here as there are not files to
        # be crawled for.
        elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld',
                                  root_path='/fake')
        elf_patcher.patch(elf_file=elf_file)

        self.assertFalse(check_call_mock.called)
Exemple #10
0
    def test_patch(self):
        elf_file = self.fake_elf['fake_elf-2.23']
        # The base_path does not matter here as there are not files to
        # be crawled for.
        elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld',
                                  root_path='/fake')
        elf_patcher.patch(elf_file=elf_file)

        self.check_call_mock.assert_called_once_with([
            self.expected_patchelf, '--set-interpreter', '/lib/fake-ld',
            elf_file.path
        ])
Exemple #11
0
    def test_patch(self):
        elf_file = elf.ElfFile(path='/fake-elf', magic=self.stub_magic)
        # The base_path does not matter here as there are not files to
        # be crawled for.
        elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld',
                                  root_path='/fake')
        elf_patcher.patch(elf_file=elf_file)

        self.check_call_mock.assert_called_once_with([
            self.expected_patchelf, '--set-interpreter', '/lib/fake-ld',
            '/fake-elf'
        ])
Exemple #12
0
    def test_patch(self, check_call_mock):
        stub_magic = ('ELF 64-bit LSB executable, x86-64, version 1 (SYSV), '
                      'dynamically linked, interpreter '
                      '/lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32')
        elf_file = elf.ElfFile(path='/fake-elf', magic=stub_magic)
        elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld')
        elf_patcher.patch(elf_file=elf_file)

        check_call_mock.assert_called_once_with([
            self.expected_patchelf, '--set-interpreter', '/lib/fake-ld',
            '/fake-elf'
        ])
Exemple #13
0
    def test_patchelf_from_snap_used_if_using_snap(self):
        self.useFixture(
            fixtures.EnvironmentVariable('SNAP', '/snap/snapcraft/current'))
        self.useFixture(fixtures.EnvironmentVariable('SNAP_NAME', 'snapcraft'))
        # The base_path does not matter here as there are not files to
        # be crawled for.
        elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld',
                                  root_path='/fake')

        expected_patchelf = os.path.join('/snap', 'snapcraft', 'current',
                                         'bin', 'patchelf')
        self.assertThat(elf_patcher._patchelf_cmd, Equals(expected_patchelf))
Exemple #14
0
    def test_patch_fails_with_old_version(self):
        self.fake_elf = fixture_setup.FakeElf(root_path=self.path,
                                              patchelf_version='0.8')
        self.useFixture(self.fake_elf)

        elf_file = self.fake_elf['fake_elf-bad-patchelf']
        # The base_path does not matter here as there are not files to
        # be crawled for.
        elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld',
                                  root_path='/fake')

        self.assertRaises(errors.PatcherNewerPatchelfError,
                          elf_patcher.patch,
                          elf_file=elf_file)
Exemple #15
0
    def prime(self, force=False) -> None:
        self.makedirs()
        self.notify_part_progress('Priming')
        snap_files, snap_dirs = self.migratable_fileset_for('prime')
        _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir)

        elf_files = elf.get_elf_files(self.primedir, snap_files)
        dependencies = set()
        for elf_file in elf_files:
            dependencies.update(elf.get_dependencies(elf_file.path))

        # Split the necessary dependencies into their corresponding location.
        # We'll both migrate and track the system dependencies, but we'll only
        # track the part and staged dependencies, since they should have
        # already been primed by other means, and migrating them again could
        # potentially override the `stage` or `snap` filtering.
        (in_part, staged, primed,
         system) = _split_dependencies(dependencies, self.installdir,
                                       self.stagedir, self.primedir)

        part_dependency_paths = {os.path.dirname(d) for d in in_part}
        staged_dependency_paths = {os.path.dirname(d) for d in staged}

        dependency_paths = part_dependency_paths | staged_dependency_paths

        if not self._build_attributes.no_system_libraries():
            system_dependency_paths = {os.path.dirname(d) for d in system}
            dependency_paths.update(system_dependency_paths)

            if system:
                # Lots of dependencies are linked with a symlink, so we need to
                # make sure we follow those symlinks when we migrate the
                # dependencies.
                _migrate_files(system,
                               system_dependency_paths,
                               '/',
                               self.primedir,
                               follow_symlinks=True)

        if self._confinement == 'classic':
            dynamic_linker = self._project_options.get_core_dynamic_linker()
            elf_patcher = elf.Patcher(dynamic_linker=dynamic_linker)
            for elf_file in elf_files:
                elf_patcher.patch(elf_file=elf_file)

        self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
Exemple #16
0
    def _patch(self, dynamic_linker: str) -> None:
        preferred_patchelf_path = self._get_preferred_patchelf_path()

        elf_patcher = elf.Patcher(
            dynamic_linker=dynamic_linker,
            root_path=self._primedir,
            preferred_patchelf_path=preferred_patchelf_path)
        files_to_patch = elf.get_elf_files_to_patch(self._elf_files)
        for elf_file in files_to_patch:
            try:
                elf_patcher.patch(elf_file=elf_file)
            except errors.PatcherError as patch_error:
                logger.warning(
                    'An attempt to patch {!r} so that it would work '
                    'correctly in diverse environments was made and failed. '
                    'To disable this behavior set '
                    '`build-attributes: [no-patchelf]` for the part.'.format(
                        elf_file.path))
                if not self._is_go_based_plugin:
                    raise patch_error
Exemple #17
0
    def _patch(self, dynamic_linker: str) -> None:
        preferred_patchelf_path = self._get_preferred_patchelf_path()

        elf_patcher = elf.Patcher(
            dynamic_linker=dynamic_linker,
            root_path=self._primedir,
            preferred_patchelf_path=preferred_patchelf_path)

        # Patching all files instead of a subset of them to ensure the
        # environment is consistent and the chain of dlopens that may
        # happen remains sane.
        for elf_file in self._elf_files:
            try:
                elf_patcher.patch(elf_file=elf_file)
            except errors.PatcherError as patch_error:
                logger.warning(
                    'An attempt to patch {!r} so that it would work '
                    'correctly in diverse environments was made and failed. '
                    'To disable this behavior set '
                    '`build-attributes: [no-patchelf]` for the part.'.format(
                        elf_file.path))
                if not self._is_go_based_plugin:
                    raise patch_error
Exemple #18
0
    def prime(self, force=False) -> None:  # noqa: C901
        self.makedirs()
        self.notify_part_progress('Priming')
        snap_files, snap_dirs = self.migratable_fileset_for('prime')
        _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir)

        elf_files = elf.get_elf_files(self.primedir, snap_files)
        all_dependencies = set()
        # TODO: base snap support
        core_path = common.get_core_path()

        # Reset to take into account new data inside prime provided by other
        # parts.
        self._soname_cache.reset()
        for elf_file in elf_files:
            all_dependencies.update(
                elf_file.load_dependencies(root_path=self.primedir,
                                           core_base_path=core_path,
                                           soname_cache=self._soname_cache))

        dependency_paths = self._handle_dependencies(all_dependencies)

        if not self._build_attributes.keep_execstack():
            clear_execstack(elf_files=elf_files)

        # TODO revisit if we need to support variations and permutations
        #  of this
        staged_patchelf_path = os.path.join(self.stagedir, 'bin', 'patchelf')
        if not os.path.exists(staged_patchelf_path):
            staged_patchelf_path = None
        # We need to verify now that the GLIBC version would be compatible
        # with that of the base.
        # TODO the linker version depends on the chosen base, but that
        # base may not be installed so we cannot depend on
        # get_core_dynamic_linker to resolve the final path for which
        # we resort to our only working base 16, ld-2.23.so.
        linker_incompat = dict()  # type: Dict[str, str]
        for elf_file in elf_files:
            if not elf_file.is_linker_compatible(linker='ld-2.23.so'):
                linker_incompat[elf_file.path] = elf_file.get_required_glibc()
        # If libc6 is staged, to avoid symbol mixups we will resort to
        # glibc mangling.
        libc6_staged = 'libc6' in self._part_properties.get(
            'stage-packages', [])
        is_classic = self._confinement == 'classic'
        # classic confined snaps built on anything but a host supporting the
        # the target base will require glibc mangling.
        classic_mangling_needed = (
            is_classic
            and not self._project_options.is_host_compatible_with_base)
        if linker_incompat:
            formatted_items = [
                '- {} (requires GLIBC {})'.format(k, v)
                for k, v in linker_incompat.items()
            ]
            logger.warning(
                'The GLIBC version of the targeted core is 2.23. A newer '
                'libc will be required for the following files:\n{}'.format(
                    '\n'.join(formatted_items)))
        if (linker_incompat or libc6_staged or classic_mangling_needed):
            if not libc6_staged:
                raise errors.StagePackageMissingError(package='libc6')
            handle_glibc_mismatch(elf_files=elf_files,
                                  root_path=self.primedir,
                                  snap_base_path=self._snap_base_path,
                                  core_base_path=core_path,
                                  preferred_patchelf_path=staged_patchelf_path,
                                  soname_cache=self._soname_cache)
        elif is_classic:
            dynamic_linker = self._project_options.get_core_dynamic_linker()
            elf_patcher = elf.Patcher(
                dynamic_linker=dynamic_linker,
                root_path=self.primedir,
                preferred_patchelf_path=staged_patchelf_path)
            for elf_file in elf_files:
                elf_patcher.patch(elf_file=elf_file)

        self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
Exemple #19
0
    def test_patch_uses_snapped_strip(self):
        self.useFixture(fixture_setup.FakeSnapcraftIsASnap())
        self.fake_elf = fixture_setup.FakeElf(root_path=self.path,
                                              patchelf_version='0.8')
        self.useFixture(self.fake_elf)

        elf_file = self.fake_elf['fake_elf-bad-patchelf']

        real_check_call = subprocess.check_call
        real_check_output = subprocess.check_output
        real_exists = os.path.exists

        def _fake_check_call(*args, **kwargs):
            if 'patchelf' in args[0][0]:
                self.assertThat(args[0][0],
                                Equals('/snap/snapcraft/current/bin/patchelf'))
                args[0][0] = 'patchelf'
            elif 'strip' in args[0][0]:
                self.assertThat(
                    args[0][0],
                    Equals('/snap/snapcraft/current/usr/bin/strip'))
                args[0][0] = 'strip'
            real_check_call(*args, **kwargs)

        def _fake_check_output(*args, **kwargs):
            if 'patchelf' in args[0][0]:
                self.assertThat(args[0][0],
                                Equals('/snap/snapcraft/current/bin/patchelf'))
                args[0][0] = 'patchelf'
            elif 'strip' in args[0][0]:
                self.assertThat(
                    args[0][0],
                    Equals('/snap/snapcraft/current/usr/bin/strip'))
                args[0][0] = 'strip'
            return real_check_output(*args, **kwargs)

        def _fake_exists(path):
            if path == '/snap/snapcraft/current/bin/patchelf':
                return True
            elif path == '/snap/snapcraft/current/usr/bin/strip':
                return True
            else:
                return real_exists(path)

        with mock.patch('subprocess.check_call') as mock_check_call:
            with mock.patch('subprocess.check_output') as mock_check_output:
                with mock.patch('os.path.exists', side_effect=_fake_exists):
                    mock_check_call.side_effect = _fake_check_call
                    mock_check_output.side_effect = _fake_check_output

                    # The base_path does not matter here as there are not files
                    # for which to crawl.
                    elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld',
                                              root_path='/fake')
                    self.assertRaises(errors.PatcherNewerPatchelfError,
                                      elf_patcher.patch,
                                      elf_file=elf_file)

                    # Test that .note.go.buildid is stripped off
                    mock_check_call.assert_has_calls([
                        mock.call([
                            'patchelf', '--set-interpreter', '/lib/fake-ld',
                            mock.ANY
                        ]),
                        mock.call([
                            'strip', '--remove-section', '.note.go.buildid',
                            mock.ANY
                        ]),
                        mock.call([
                            'patchelf', '--set-interpreter', '/lib/fake-ld',
                            mock.ANY
                        ]),
                    ])
Exemple #20
0
 def test_patch_does_nothing_if_no_interpreter(self):
     elf_file = self.fake_elf["fake_elf-static"]
     # The base_path does not matter here as there are not files to
     # be crawled for.
     elf_patcher = elf.Patcher(dynamic_linker="/lib/fake-ld", root_path="/fake")
     elf_patcher.patch(elf_file=elf_file)
Exemple #21
0
 def test_patch(self):
     elf_file = self.fake_elf["fake_elf-2.23"]
     # The base_path does not matter here as there are not files to
     # be crawled for.
     elf_patcher = elf.Patcher(dynamic_linker="/lib/fake-ld", root_path="/fake")
     elf_patcher.patch(elf_file=elf_file)
Exemple #22
0
    def test_patch_uses_snapped_strip(self):
        self.useFixture(fixture_setup.FakeSnapcraftIsASnap())
        self.fake_elf = fixture_setup.FakeElf(
            root_path=self.path, patchelf_version="0.8"
        )
        self.useFixture(self.fake_elf)

        elf_file = self.fake_elf["fake_elf-bad-patchelf"]

        real_check_call = subprocess.check_call
        real_check_output = subprocess.check_output
        real_exists = os.path.exists

        def _fake_check_call(*args, **kwargs):
            if "patchelf" in args[0][0]:
                self.assertThat(
                    args[0][0], Equals("/snap/snapcraft/current/bin/patchelf")
                )
                args[0][0] = "patchelf"
            elif "strip" in args[0][0]:
                self.assertThat(
                    args[0][0], Equals("/snap/snapcraft/current/usr/bin/strip")
                )
                args[0][0] = "strip"
            real_check_call(*args, **kwargs)

        def _fake_check_output(*args, **kwargs):
            if "patchelf" in args[0][0]:
                self.assertThat(
                    args[0][0], Equals("/snap/snapcraft/current/bin/patchelf")
                )
                args[0][0] = "patchelf"
            elif "strip" in args[0][0]:
                self.assertThat(
                    args[0][0], Equals("/snap/snapcraft/current/usr/bin/strip")
                )
                args[0][0] = "strip"
            return real_check_output(*args, **kwargs)

        def _fake_exists(path):
            if path == "/snap/snapcraft/current/bin/patchelf":
                return True
            elif path == "/snap/snapcraft/current/usr/bin/strip":
                return True
            else:
                return real_exists(path)

        with mock.patch("subprocess.check_call") as mock_check_call:
            with mock.patch("subprocess.check_output") as mock_check_output:
                with mock.patch("os.path.exists", side_effect=_fake_exists):
                    mock_check_call.side_effect = _fake_check_call
                    mock_check_output.side_effect = _fake_check_output

                    # The base_path does not matter here as there are not files
                    # for which to crawl.
                    elf_patcher = elf.Patcher(
                        dynamic_linker="/lib/fake-ld", root_path="/fake"
                    )
                    self.assertRaises(
                        errors.PatcherNewerPatchelfError,
                        elf_patcher.patch,
                        elf_file=elf_file,
                    )

                    # Test that .note.go.buildid is stripped off
                    mock_check_call.assert_has_calls(
                        [
                            mock.call(
                                [
                                    "patchelf",
                                    "--set-interpreter",
                                    "/lib/fake-ld",
                                    mock.ANY,
                                ]
                            ),
                            mock.call(
                                [
                                    "strip",
                                    "--remove-section",
                                    ".note.go.buildid",
                                    mock.ANY,
                                ]
                            ),
                            mock.call(
                                [
                                    "patchelf",
                                    "--set-interpreter",
                                    "/lib/fake-ld",
                                    mock.ANY,
                                ]
                            ),
                        ]
                    )
Exemple #23
0
    def prime(self, force=False) -> None:
        self.makedirs()
        self.notify_part_progress('Priming')
        snap_files, snap_dirs = self.migratable_fileset_for('prime')
        _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir)

        elf_files = elf.get_elf_files(self.primedir, snap_files)
        all_dependencies = set()
        # TODO: base snap support
        core_path = common.get_core_path()

        for elf_file in elf_files:
            all_dependencies.update(
                elf_file.load_dependencies(root_path=self.primedir,
                                           core_base_path=core_path))

        # Split the necessary dependencies into their corresponding location.
        # We'll both migrate and track the system dependencies, but we'll only
        # track the part and staged dependencies, since they should have
        # already been primed by other means, and migrating them again could
        # potentially override the `stage` or `snap` filtering.
        (in_part, staged, primed,
         system) = _split_dependencies(all_dependencies, self.installdir,
                                       self.stagedir, self.primedir)

        part_dependency_paths = {os.path.dirname(d) for d in in_part}
        staged_dependency_paths = {os.path.dirname(d) for d in staged}

        dependency_paths = part_dependency_paths | staged_dependency_paths

        if not self._build_attributes.no_system_libraries():
            system_dependency_paths = {os.path.dirname(d) for d in system}
            dependency_paths.update(system_dependency_paths)

            if system:
                # Lots of dependencies are linked with a symlink, so we need to
                # make sure we follow those symlinks when we migrate the
                # dependencies.
                _migrate_files(system,
                               system_dependency_paths,
                               '/',
                               self.primedir,
                               follow_symlinks=True)
                formatted_system = '\n'.join(sorted(system))
                logger.warning(
                    'Files from the build host were migrated into the snap to '
                    'satisfy dependencies that would otherwise not be met. '
                    'This feature will be removed in a future release. If '
                    'these libraries are needed in the final snap, ensure '
                    'that the following are either satisfied by a '
                    'stage-packages entry or through a part:\n{}'.format(
                        formatted_system))

        # TODO revisit if we need to support variations and permutations
        #  of this
        staged_patchelf_path = os.path.join(self.stagedir, 'bin', 'patchelf')
        if not os.path.exists(staged_patchelf_path):
            staged_patchelf_path = None
        # We need to verify now that the GLIBC version would be compatible
        # with that of the base.
        # TODO the linker version depends on the chosen base, but that
        # base may not be installed so we cannot depend on
        # get_core_dynamic_linker to resolve the final path for which
        # we resort to our only working base 16, ld-2.23.so.
        linker_compatible = (e.is_linker_compatible(linker='ld-2.23.so')
                             for e in elf_files)
        if not all((x for x in linker_compatible)):
            handle_glibc_mismatch(elf_files=elf_files,
                                  root_path=self.primedir,
                                  snap_base_path=self._snap_base_path,
                                  core_base_path=core_path,
                                  preferred_patchelf_path=staged_patchelf_path)
        elif self._confinement == 'classic':
            dynamic_linker = self._project_options.get_core_dynamic_linker()
            elf_patcher = elf.Patcher(
                dynamic_linker=dynamic_linker,
                root_path=self.primedir,
                preferred_patchelf_path=staged_patchelf_path)
            for elf_file in elf_files:
                elf_patcher.patch(elf_file=elf_file)

        self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
Exemple #24
0
    def prime(self, force=False) -> None:
        self.makedirs()
        self.notify_part_progress('Priming')
        snap_files, snap_dirs = self.migratable_fileset_for('prime')
        _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir)

        elf_files = elf.get_elf_files(self.primedir, snap_files)
        all_dependencies = set()
        for elf_file in elf_files:
            all_dependencies.update(elf_file.load_dependencies())

        # Split the necessary dependencies into their corresponding location.
        # We'll both migrate and track the system dependencies, but we'll only
        # track the part and staged dependencies, since they should have
        # already been primed by other means, and migrating them again could
        # potentially override the `stage` or `snap` filtering.
        (in_part, staged, primed, system) = _split_dependencies(
            all_dependencies, self.installdir, self.stagedir, self.primedir)

        part_dependency_paths = {os.path.dirname(d) for d in in_part}
        staged_dependency_paths = {os.path.dirname(d) for d in staged}

        dependency_paths = part_dependency_paths | staged_dependency_paths

        if not self._build_attributes.no_system_libraries():
            system_dependency_paths = {os.path.dirname(d) for d in system}
            dependency_paths.update(system_dependency_paths)

            if system:
                # Lots of dependencies are linked with a symlink, so we need to
                # make sure we follow those symlinks when we migrate the
                # dependencies.
                _migrate_files(system, system_dependency_paths, '/',
                               self.primedir, follow_symlinks=True)

        # TODO: base snap support
        core_path = common.get_core_path()

        def mangle_library_path(library_path: str, elf_file_path: str) -> str:
            # If the path is is in the core snap, use the absolute path,
            # if the path is primed, use $ORIGIN, and last if the dependency
            # is not anywhere return an empty string.
            #
            # Once we move away from the system library grabbing logic
            # we can move to a smarter library capturing mechanism.
            library_path = library_path.replace(self.installdir, self.primedir)
            if library_path.startswith(core_path):
                return os.path.dirname(library_path)
            elif (library_path.startswith(self.primedir) and
                  os.path.exists(library_path)):
                rel_library_path = os.path.relpath(library_path, elf_file_path)
                rel_library_path_dir = os.path.dirname(rel_library_path)
                # return the dirname, with the first .. replace with $ORIGIN
                return rel_library_path_dir.replace('..', '$ORIGIN', 1)
            else:
                return ''

        if self._confinement == 'classic':
            core_rpaths = common.get_library_paths(
                core_path, self._project_options.arch_triplet,
                existing_only=False)
            dynamic_linker = self._project_options.get_core_dynamic_linker()
            elf_patcher = elf.Patcher(dynamic_linker=dynamic_linker,
                                      library_path_func=mangle_library_path,
                                      base_rpaths=core_rpaths)
            for elf_file in elf_files:
                elf_patcher.patch(elf_file=elf_file)

        self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
Exemple #25
0
    def test_patch_does_nothing_if_no_interpreter(self, check_call_mock):
        elf_file = elf.ElfFile(path='/fake-elf', is_executable=False)
        elf_patcher = elf.Patcher(dynamic_linker='/lib/fake-ld')
        elf_patcher.patch(elf_file=elf_file)

        self.assertFalse(check_call_mock.called)
Exemple #26
0
def handle_glibc_mismatch(*,
                          elf_files: FrozenSet[elf.ElfFile],
                          root_path: str,
                          core_base_path: str,
                          snap_base_path: str,
                          preferred_patchelf_path=None) -> None:
    """Copy over libc6 libraries from the host and patch necessary elf files.

    If no newer glibc version is detected in elf_files, this function returns.
    The dynamic linker and related libraries to libc6 are expected to be found
    in root_path.

    :param snapcraft.internal.elf.ElfFile elf_files:
        set of candidate elf files to patch if a newer libc6 is required.
    :param str root_path: the root path of a snap tree.
    :param str core_base_path: the path to the base snap.
    :param str snap_base_path: absolute path to the snap once installed to
                               setup proper rpaths.
    :param str preferred_patchelf_path: patch the necessary elf_files with
                                        this patchelf.
    """
    formatted_list = list()  # type: List[str]
    patch_elf_files = list()  # type: List[elf.ElfFile]
    for elf_file in elf_files:
        required_glibc = elf_file.get_required_glibc()
        if not required_glibc:
            continue

        # We need to verify now that the GLIBC version would be compatible
        # with that of the base.
        # TODO the linker version depends on the chosen base, but that
        # base may not be installed so we cannot depend on
        # get_core_dynamic_linker to resolve the final path for which
        # we resort to our only working base 16, ld-2.23.so.
        if not elf_file.is_linker_compatible(linker='ld-2.23.so'):
            formatted_list.append('- {} -> GLIBC {}'.format(
                elf_file.path, required_glibc))
            patch_elf_files.append(elf_file)

    if not patch_elf_files:
        return

    logger.warning('The primed files will not work with the current '
                   'base given the GLIBC mismatch of the primed '
                   'files and the linker version (2.23) used in the '
                   'base. These are the GLIBC versions required by '
                   'the primed files that do not match and will be '
                   'patched:\n{}\n'.format('\n'.join(formatted_list)))
    # We assume the current system will satisfy the GLIBC requirement,
    # get the current libc6 libraries (which includes the linker)
    libc6_libraries_list = repo.Repo.get_package_libraries('libc6')

    # For security reasons, we do not want to automatically pull in
    # libraries but expect them to be consciously brought in by stage-packages
    # instead.
    libc6_libraries_paths = [
        os.path.join(root_path, l[1:]) for l in libc6_libraries_list
    ]

    dynamic_linker = _get_dynamic_linker(libc6_libraries_paths)

    # Get the path to the "would be" dynamic linker when this snap is
    # installed. Strip the root_path from the retrieved dynamic_linker
    # variables + the leading `/` so that os.path.join can perform the
    # proper join with snap_base_path.
    dynamic_linker_path = os.path.join(snap_base_path,
                                       dynamic_linker[len(root_path) + 1:])

    elf_patcher = elf.Patcher(dynamic_linker=dynamic_linker_path,
                              root_path=root_path,
                              preferred_patchelf_path=preferred_patchelf_path)
    for elf_file in patch_elf_files:
        # Search for dependencies again now that the new libc6 is
        # migrated.
        elf_file.load_dependencies(root_path=root_path,
                                   core_base_path=core_base_path)
        elf_patcher.patch(elf_file=elf_file)
Exemple #27
0
def handle_glibc_mismatch(*,
                          elf_files: FrozenSet[elf.ElfFile],
                          root_path: str,
                          core_base_path: str,
                          snap_base_path: str,
                          preferred_patchelf_path=None) -> None:
    """Copy over libc6 libraries from the host and patch necessary elf files.

    If no newer glibc version is detected in elf_files, this function returns.

    :param snapcraft.internal.elf.ElfFile elf_files:
        set of candidate elf files to patch if a newer libc6 is required.
    :param str root_path: the root path of a snap tree.
    :param str core_base_path: the path to the base snap.
    :param str snap_base_path: absolute path to the snap once installed to
                               setup proper rpaths.
    :param str preferred_patchelf_path: patch the necessary elf_files with
                                        this patchelf.
    """
    formatted_list = list()  # type: List[str]
    patch_elf_files = list()  # type: List[elf.ElfFile]
    for elf_file in elf_files:
        required_glibc = elf_file.get_required_glibc()
        if not required_glibc:
            continue

        # We need to verify now that the GLIBC version would be compatible
        # with that of the base.
        # TODO the linker version depends on the chosen base, but that
        # base may not be installed so we cannot depend on
        # get_core_dynamic_linker to resolve the final path for which
        # we resort to our only working base 16, ld-2.23.so.
        if not elf_file.is_linker_compatible(linker='ld-2.23.so'):
            formatted_list.append('- {} -> GLIBC {}'.format(
                elf_file.path, required_glibc))
            patch_elf_files.append(elf_file)

    if not patch_elf_files:
        return

    logger.warning('The primed files will not work with the current '
                   'base given the GLIBC mismatch of the primed '
                   'files and the linker version (2.23) used in the '
                   'base. These are the GLIBC versions required by '
                   'the primed files that do not match and will be '
                   'patched:\n {}\n'
                   'To work around this, the newer libc will be '
                   'migrated into the snap, and these files will be '
                   'patched to use it.'.format('\n'.join(formatted_list)))
    # We assume the current system will satisfy the GLIBC requirement,
    # get the current libc6 libraries (which includes the linker)
    libc6_libraries = repo.Repo.get_package_libraries('libc6')
    libc6_path = os.path.join('snap', 'libc6')

    # Before doing anything else, verify there's a dynamic linker we can use.
    dynamic_linker_path = os.path.join(snap_base_path, libc6_path,
                                       _get_dynamic_linker(libc6_libraries))

    dest_dir = os.path.join(root_path, libc6_path)
    os.makedirs(dest_dir, exist_ok=True)

    for src in libc6_libraries:
        dst = os.path.join(dest_dir, os.path.basename(src))
        # follow_symlinks is set to True for elf crawling to work.
        file_utils.link_or_copy(src, dst, follow_symlinks=True)

    elf_patcher = elf.Patcher(dynamic_linker=dynamic_linker_path,
                              root_path=root_path,
                              preferred_patchelf_path=preferred_patchelf_path)
    for elf_file in patch_elf_files:
        # Search for dependencies again now that the new libc6 is
        # migrated.
        elf_file.load_dependencies(root_path=root_path,
                                   core_base_path=core_base_path)
        elf_patcher.patch(elf_file=elf_file)