F44 Change Proposal: Hardlink identical files in packages by default (self-contained)

Hardlink identical files in packages by default

This is a proposed Change for Fedora Linux.
This document represents a proposed Change. As part of the Changes process, proposals are publicly announced in order to receive community feedback. This proposal will only be implemented if approved by the Fedora Engineering Steering Committee.

Wiki
Announced

:link: Summary

A post-build step is added to the package build macros to automatically hardlink all identical files under /usr. Previously, this was done in some packages and now it’s done everywhere by default.

:link: Owner

:link: Detailed Description

Files can be hardlinked at the end of the %install step in package builds. rpm supports this and will preserve those links in the binary rpm and during installation. This makes the installation a bit more efficient. Hardlinking of read-only files is generally transparent to the user, but has some small benefits: the files are not duplicated in the file system; backup, copy, and search programs will usually make use of the link information and not process the same inode twice. Thus, it’s good to hardlink as many packaged files as possible.

Previously, hardlinking was done automatically for a subset of files in Python packages (via the %__os_install_post_python macro), and explicitly in some packages with lots of similar files (usually via the hardlink program).

The %__os_install_post is extended to automatically hardlink all identical files under %{buildroot}%{_prefix}, i.e. the /usr directory in packages. This calls a new helper binary (part of the add-determinism package) that does the linking.

Hard links may be confusing if the file is modified. In particular, all links to the same inode share the same ownership and permissions, and obviously the same contents. Thus, we want to apply hardlinking only to files under /usr, which are generally read-only in packages.

When files are hardlinked, mtime (the modification timestamp) is taken into account. Only files with identical mtime, owner, group, and mode are subject to linking. The new program written to do the linking takes $SOURCE_DATE_EPOCH into account, and will clamp mtimes to it before comparing.

Note: rpm correctly handles the case where a hardlink is between files in two different subpackages. Thus, we can hardlink everything under %{buildroot}, and rpm will store the files as hardlinked if they are in the same output package, adjusting the hardlink counts as appropriate.

:link: Feedback

:link: Benefit to Fedora

As mentioned in the Summary, hardlinking deduplicates the data in rpms and in installations. Backup, copy, and search programs will usually make use of the link information and not process the same inode twice. Thus, by hardlinking files in the packages we make things a bit more efficient. (The impact is small, because rpms generally don’t have large duplicated files.)

Hardlinking of files was previously done in some packages explicitly, but it required adding a BuildRequires line and invoking a script, so it wasn’t done very often. By handling this automatically, we’ll be able to simplify those packages.

Another caveat that needs to be taken into account when doing hardlinking as part of the package build is that newer hardlink versions use reflinks instead of hardlinks by default. (With a hardlink, one inode is connected to the file system tree in two or more places. With a reflink, some blocks of an inode are shared with another inode, inside of the file system, and the two inodes retain their separate identities.) rpm has no knowledge of reflinks, so those reflinks created during package build have no effect on the binary package and the payload is duplicated. Invocations of hardlink would have to be annotated with --reflink=never to retain the intended effect. By removing that step from packages we avoid this issue.

The Reproducible Builds effort reported that some packages that use hardlinking are not reproducible, see irreproducibility#22. When files are created in the package build, depending on how fast the build machine is, some files might or might not have identical timestamps. The tools that were used to compare files for hardlinking were general tools that did not “know” that we’d clamp the mtimes to $SOURCE_DATE_EPOCH in a subsequent step, so the results of the mtime comparisons were unstable. The tool that is added as part of this Change does the mtime clamping internally for reproducible results. Fixing this issue was the initial motivation for this change.

:link: Scope

  • Proposal owners:

    • extend the add-determinism package with a little helper that does file comparisons and hardlinks identical files. The helper takes $SOURCE_DATE_EPOCH into account.
    • open pull request for redhat-rpm-config to insert a call to the helper in %__os_install_post.
    • open pull request for python-srpm-macros to drop their hardlinking step.
  • Other developers:

    • merge pull request
    • report issues if the hardlinking has unforeseen consequences or does not work correctly.
    • drop explicit calls to hardlink in their packages.
  • Release engineering:

  • Policies and guidelines: not needed, AFAICT.

  • Trademark approval: N/A (not needed for this Change)

  • Alignment with the Fedora Strategy:

:link: Upgrade/compatibility impact

No impact.

:link: Early Testing (Optional)

Build package with an invocation of the new helper.

:link: How To Test

Install packages rebuilt with the helper.

:link: User Experience

Not visible to users.

:link: Dependencies

:link: Contingency Plan

  • Contingency mechanism:
    • if hardlinking causes a problem in some specific packages, they can be trivially modified to skip the hardlinking step by setting a macro.
    • if there is a general problem, we can easily drop the macro in redhat-rpm-config.
  • Contingency deadline: any time, even after release. Any affected packages would have to be rebuilt.
  • Blocks release? No.

:link: Documentation

The invocation of the helper will be documented inline in the macros files. Other documentation is not needed.

:link: Release Notes

Package builds automatically hardlink identical files. This reduces the installation footprint a bit and also makes packages builds more reproducible.

Last edited by @amoloney 2025-09-04T12:01:14Z

Last edited by @amoloney 2025-09-04T12:01:14Z

2 Likes

How do you feel about the proposal as written?

  • Strongly in favor
  • In favor, with reservations
  • Neutral
  • Opposed, but could be convinced
  • Strongly opposed
0 voters

If you are in favor but have reservations, or are opposed but something could change your mind, please explain in a reply.

We want everyone to be heard, but many posts repeating the same thing actually makes that harder. If you have something new to say, please say it. If, instead, you find someone has already covered what you’d like to express, please simply give that post a :heart: instead of reiterating. You can even do this by email, by replying with the heart emoji or just “+1”. This will make long topics easier to follow.

Please note that this is an advisory “straw poll” meant to gauge sentiment. It isn’t a vote or a scientific survey. See About the Change Proposals category for more about the Change Process and moderation policy.

I see a gap between the change title and the description.

The description - as I understand it - says the change will only be done to read-only files under /usr.
Which would make sense to me :+1:,
but I’d like to see this specified in the Change title too.

“Read-only” is an overloaded term. I wanted to say that they are not modified after installation. Whether the files actually cannot be written to depends on how the system is installed. On ostree systemd or systems with immutable /usr, they would indeed by read-only, even for root. But on normal package-based installations, the files are not read-only, in the sense that a sufficiently privileged user could modify them. Nevertheless, this is not supposed to happen, so they are read-only in the sense of intended use.

The title is very long already. I don’t think trying to make it even longer would be a good idea.

Are we planning to hard link files from multiple RPMs, or just a single RPM?

Are we planning to hard link files from multiple RPMs, or just a single RPM?

Single rpms. This is a build-time operation, so operations that cross rpms boundaries are not possible.

3 Likes

We cannot drop that unconditionally because add-determinism is missing from Fedora ELN.

I have opened Add add-determinism to ELN · Issue #292 · fedora-eln/eln · GitHub to maybe include add-determinism in ELN. But until that happens, this will be one more thing we carry in Fedora only because of RHEL (marhalparser being the other one).

Oh, that’s a bummer. But it should be possible to conditionalize this. python-srpm-macros will not be simpler, but the actual runtime will be a bit simpler.

If the two copies of the file would have different SELinux contexts, would that be discovered in the build phase so those copies would not be linked? Hardlink doesn’t seem to check the SELinux contexts. As I understand it SELinux contexts are applied at installation time, and not part of the package.

That’s a good question. I think in practice it won’t be much of an issue, because there is little reason for files from the same package with the same contents to have different SELinux contexts.

On one VM here, a great majority of the files use usr_t or lib_t…

$ find /usr -type f -exec ls -lZ {} \; | cut -d' ' -f5 | sort | uniq -c | sort -gr
  61056 system_u:object_r:usr_t:s0
  34549 system_u:object_r:lib_t:s0
  15403 system_u:object_r:locale_t:s0
  13799 system_u:object_r:modules_object_t:s0
   8652 system_u:object_r:man_t:s0
   2824 system_u:object_r:bin_t:s0
    926 unconfined_u:object_r:usr_t:s0
    754 system_u:object_r:cupsd_etc_t:s0
    549 system_u:object_r:systemd_unit_file_t:s0
    279 system_u:object_r:fonts_t:s0
    259 system_u:object_r:textrel_shlib_t:s0
    203 system_u:object_r:systemd_conf_t:s0
    108 system_u:object_r:httpd_modules_t:s0
     67 system_u:object_r:cupsd_exec_t:s0
     65 system_u:object_r:xdm_unit_file_t:s0
     57 system_u:object_r:fsadm_exec_t:s0
     44 system_u:object_r:init_exec_t:s0
     42 system_u:object_r:virtd_unit_file_t:s0
     31 unconfined_u:object_r:fonts_cache_t:s0
     29 system_u:object_r:power_unit_file_t:s0
     29 system_u:object_r:bootloader_exec_t:s0
     25 unconfined_u:object_r:rpm_var_lib_t:s0
     22 unconfined_u:object_r:modules_dep_t:s0
     21 system_u:object_r:modules_dep_t:s0
     18 system_u:object_r:alsa_etc_rw_t:s0
     15 system_u:object_r:sssd_unit_file_t:s0
     14 system_u:object_r:mount_exec_t:s0
     13 system_u:object_r:cockpit_unit_file_t:s0
     11 unconfined_u:object_r:modules_object_t:s0
     11 system_u:object_r:lpr_exec_t:s0
      8 system_u:object_r:sssd_exec_t:s0
      7 system_u:object_r:thumb_exec_t:s0
      7 system_u:object_r:shell_exec_t:s0
      7 system_u:object_r:nfsd_unit_file_t:s0
      7 system_u:object_r:crack_db_t:s0
      6 unconfined_u:object_r:lib_t:s0
      6 system_u:object_r:systemd_generic_generator_exec_t:s0
      6 system_u:object_r:rpcd_unit_file_t:s0
      6 system_u:object_r:lvm_exec_t:s0
      6 system_u:object_r:ifconfig_exec_t:s0
      6 system_u:object_r:getty_unit_file_t:s0
      6 system_u:object_r:abrt_unit_file_t:s0
      5 system_u:object_r:rpm_var_lib_t:s0
      5 system_u:object_r:rpm_exec_t:s0
      5 system_u:object_r:iscsi_unit_file_t:s0
      5 system_u:object_r:hwdata_t:s0
      5 system_u:object_r:httpd_unit_file_t:s0
      5 system_u:object_r:crack_exec_t:s0
      5 system_u:object_r:admin_passwd_exec_t:s0
      5 system_u:object_r:abrt_dump_oops_exec_t:s0
      4 system_u:object_r:useradd_exec_t:s0
      4 system_u:object_r:semanage_exec_t:s0
      4 system_u:object_r:rpcd_exec_t:s0
      4 system_u:object_r:mdadm_unit_file_t:s0
      4 system_u:object_r:groupadd_exec_t:s0
      4 system_u:object_r:dmidecode_exec_t:s0
      4 system_u:object_r:dbusd_exec_t:s0
      4 system_u:object_r:cupsd_unit_file_t:s0
      4 system_u:object_r:abrt_exec_t:s0
      3 system_u:object_r:virtd_exec_t:s0
      3 system_u:object_r:traceroute_exec_t:s0
      3 system_u:object_r:systemd_timedated_unit_file_t:s0
      3 system_u:object_r:systemd_timedated_exec_t:s0
      3 system_u:object_r:sshd_unit_file_t:s0
      3 system_u:object_r:quota_exec_t:s0
      3 system_u:object_r:pinentry_exec_t:s0
      3 system_u:object_r:passwd_exec_t:s0
      3 system_u:object_r:nfsd_exec_t:s0
      3 system_u:object_r:NetworkManager_unit_file_t:s0
      3 system_u:object_r:NetworkManager_exec_t:s0
      3 system_u:object_r:mozilla_exec_t:s0
      3 system_u:object_r:mdadm_exec_t:s0
      3 system_u:object_r:lvm_unit_file_t:s0
      3 system_u:object_r:iscsid_exec_t:s0
      3 system_u:object_r:iptables_exec_t:s0
      3 system_u:object_r:httpd_exec_t:s0
      3 system_u:object_r:fwupd_unit_file_t:s0
      3 system_u:object_r:cockpit_ws_exec_t:s0
      3 system_u:object_r:chronyd_unit_file_t:s0
      2 unconfined_u:object_r:init_exec_t:s0
      2 system_u:object_r:xserver_exec_t:s0
      2 system_u:object_r:xdm_exec_t:s0
      2 system_u:object_r:vmtools_exec_t:s0
      2 system_u:object_r:virtlogd_exec_t:s0
      2 system_u:object_r:updpwd_exec_t:s0
      2 system_u:object_r:systemd_userdbd_unit_file_t:s0
      2 system_u:object_r:systemd_userdbd_exec_t:s0
      2 system_u:object_r:systemd_nsresourced_exec_t:s0
      2 system_u:object_r:systemd_mountfsd_exec_t:s0
      2 system_u:object_r:systemd_importd_exec_t:s0
      2 system_u:object_r:systemd_homed_unit_file_t:s0
      2 system_u:object_r:ssh_exec_t:s0
      2 system_u:object_r:sshd_keygen_unit_file_t:s0
      2 system_u:object_r:ssh_agent_exec_t:s0
      2 system_u:object_r:qemu_exec_t:s0
      2 system_u:object_r:qatlib_exec_t:s0
      2 system_u:object_r:pppd_exec_t:s0
      2 system_u:object_r:policykit_auth_exec_t:s0
      2 system_u:object_r:pasta_exec_t:s0
      2 system_u:object_r:passt_exec_t:s0
      2 system_u:object_r:NetworkManager_dispatcher_chronyc_script_t:s0
      2 system_u:object_r:netutils_exec_t:s0
      2 system_u:object_r:named_unit_file_t:s0
      2 system_u:object_r:keyutils_request_exec_t:s0
      2 system_u:object_r:gpg_exec_t:s0
      2 system_u:object_r:gnome_initial_setup_exec_t:s0
      2 system_u:object_r:gnome_atspi_exec_t:s0
      2 system_u:object_r:fwupd_exec_t:s0
      2 system_u:object_r:fusermount_exec_t:s0
      2 system_u:object_r:dhcpc_exec_t:s0
      2 system_u:object_r:dbusd_unit_file_t:s0
      2 system_u:object_r:cupsd_config_exec_t:s0
      2 system_u:object_r:container_runtime_exec_t:s0
      2 system_u:object_r:cifs_helper_exec_t:s0
      2 system_u:object_r:chfn_exec_t:s0
      2 system_u:object_r:cert_t:s0
      2 system_u:object_r:bluetooth_unit_file_t:s0
      2 system_u:object_r:avahi_unit_file_t:s0
      2 system_u:object_r:alsa_unit_file_t:s0
      2 system_u:object_r:alsa_exec_t:s0
      2 system_u:object_r:abrt_handle_event_exec_t:s0
      1 unconfined_u:object_r:systemd_getty_generator_exec_t:s0
      1 system_u:object_r:wpa_cli_exec_t:s0
      1 system_u:object_r:vpnc_exec_t:s0
      1 system_u:object_r:vmtools_unit_file_t:s0
      1 system_u:object_r:vmtools_helper_exec_t:s0
      1 system_u:object_r:vlock_exec_t:s0
      1 system_u:object_r:virtstoraged_exec_t:s0
      1 system_u:object_r:virtsecretd_exec_t:s0
      1 system_u:object_r:virt_qemu_ga_exec_t:s0
      1 system_u:object_r:virtqemud_exec_t:s0
      1 system_u:object_r:virtproxyd_exec_t:s0
      1 system_u:object_r:virtnwfilterd_exec_t:s0
      1 system_u:object_r:virtnodedevd_exec_t:s0
      1 system_u:object_r:virtnetworkd_exec_t:s0
      1 system_u:object_r:virtinterfaced_exec_t:s0
      1 system_u:object_r:virt_bridgehelper_exec_t:s0
      1 system_u:object_r:virsh_exec_t:s0
      1 system_u:object_r:vdagent_exec_t:s0
      1 system_u:object_r:utempter_exec_t:s0
      1 system_u:object_r:userhelper_exec_t:s0
      1 system_u:object_r:usbmuxd_unit_file_t:s0
      1 system_u:object_r:usbmuxd_exec_t:s0
      1 system_u:object_r:udev_exec_t:s0
      1 system_u:object_r:systemd_zram_generator_exec_t:s0
      1 system_u:object_r:systemd_zram_generator_conf_t:s0
      1 system_u:object_r:systemd_vconsole_unit_file_t:s0
      1 system_u:object_r:systemd_user_runtimedir_exec_t:s0
      1 system_u:object_r:systemd_tpm2_generator_exec_t:s0
      1 system_u:object_r:systemd_tmpfiles_exec_t:s0
      1 system_u:object_r:systemd_sysv_generator_exec_t:s0
      1 system_u:object_r:systemd_systemctl_exec_t:s0
      1 system_u:object_r:systemd_sysctl_exec_t:s0
      1 system_u:object_r:systemd_ssh_generator_exec_t:s0
      1 system_u:object_r:systemd_socket_proxyd_exec_t:s0
      1 system_u:object_r:systemd_sleep_exec_t:s0
      1 system_u:object_r:systemd_rfkill_unit_file_t:s0
      1 system_u:object_r:systemd_rfkill_exec_t:s0
      1 system_u:object_r:systemd_resolved_unit_file_t:s0
      1 system_u:object_r:systemd_resolved_exec_t:s0
      1 system_u:object_r:systemd_rc_local_generator_exec_t:s0
      1 system_u:object_r:systemd_pstore_exec_t:s0
      1 system_u:object_r:systemd_pcrlock_exec_t:s0
      1 system_u:object_r:systemd_pcrextend_exec_t:s0
      1 system_u:object_r:systemd_passwd_agent_exec_t:s0
      1 system_u:object_r:systemd_notify_exec_t:s0
      1 system_u:object_r:systemd_network_generator_exec_t:s0
      1 system_u:object_r:systemd_networkd_unit_file_t:s0
      1 system_u:object_r:systemd_networkd_exec_t:s0
      1 system_u:object_r:systemd_modules_load_unit_file_t:s0
      1 system_u:object_r:systemd_modules_load_exec_t:s0
      1 system_u:object_r:systemd_machined_unit_file_t:s0
      1 system_u:object_r:systemd_machined_exec_t:s0
      1 system_u:object_r:systemd_logind_exec_t:s0
      1 system_u:object_r:systemd_localed_exec_t:s0
      1 system_u:object_r:systemd_import_generator_exec_t:s0
      1 system_u:object_r:systemd_hwdb_unit_file_t:s0
      1 system_u:object_r:systemd_hwdb_exec_t:s0
      1 system_u:object_r:systemd_hostnamed_exec_t:s0
      1 system_u:object_r:systemd_homework_exec_t:s0
      1 system_u:object_r:systemd_homed_exec_t:s0
      1 system_u:object_r:systemd_hibernate_resume_exec_t:s0
      1 system_u:object_r:systemd_gpt_generator_exec_t:s0
      1 system_u:object_r:systemd_getty_generator_exec_t:s0
      1 system_u:object_r:systemd_fstab_generator_exec_t:s0
      1 system_u:object_r:systemd_factory_reset_generator_exec_t:s0
      1 system_u:object_r:systemd_debug_generator_exec_t:s0
      1 system_u:object_r:systemd_cryptsetup_generator_exec_t:s0
      1 system_u:object_r:systemd_coredump_exec_t:s0
      1 system_u:object_r:systemd_bless_boot_generator_exec_t:s0
      1 system_u:object_r:syslogd_exec_t:s0
      1 system_u:object_r:swtpm_exec_t:s0
      1 system_u:object_r:switcheroo_control_exec_t:s0
      1 system_u:object_r:sulogin_exec_t:s0
      1 system_u:object_r:su_exec_t:s0
      1 system_u:object_r:sudo_exec_t:s0
      1 system_u:object_r:ssh_keygen_exec_t:s0
      1 system_u:object_r:sshd_keygen_exec_t:s0
      1 system_u:object_r:sshd_exec_t:s0
      1 system_u:object_r:speech_dispatcher_unit_file_t:s0
      1 system_u:object_r:speech_dispatcher_exec_t:s0
      1 system_u:object_r:sosreport_exec_t:s0
      1 system_u:object_r:smartdwarn_script_t:s0
      1 system_u:object_r:showmount_exec_t:s0
      1 system_u:object_r:setsebool_exec_t:s0
      1 system_u:object_r:setfiles_exec_t:s0
      1 system_u:object_r:selinux_autorelabel_generator_exec_t:s0
      1 system_u:object_r:screen_exec_t:s0
      1 system_u:object_r:rtkit_daemon_exec_t:s0
      1 system_u:object_r:rsync_exec_t:s0
      1 system_u:object_r:rpmdb_exec_t:s0
      1 system_u:object_r:rpcbind_unit_file_t:s0
      1 system_u:object_r:rpcbind_exec_t:s0
      1 system_u:object_r:realmd_exec_t:s0
      1 system_u:object_r:qatlib_unit_file_t:s0
      1 system_u:object_r:pptp_exec_t:s0
      1 system_u:object_r:policykit_exec_t:s0
      1 system_u:object_r:ping_exec_t:s0
      1 system_u:object_r:pcscd_exec_t:s0
      1 system_u:object_r:passt_repair_exec_t:s0
      1 system_u:object_r:pam_timestamp_exec_t:s0
      1 system_u:object_r:openvpn_exec_t:s0
      1 system_u:object_r:oddjob_mkhomedir_exec_t:s0
      1 system_u:object_r:obex_exec_t:s0
      1 system_u:object_r:numad_unit_file_t:s0
      1 system_u:object_r:numad_exec_t:s0
      1 system_u:object_r:nfsidmap_exec_t:s0
      1 system_u:object_r:NetworkManager_priv_helper_exec_t:s0
      1 system_u:object_r:NetworkManager_dispatcher_script_t:s0
      1 system_u:object_r:NetworkManager_dispatcher_iscsid_script_t:s0
      1 system_u:object_r:NetworkManager_dispatcher_exec_t:s0
      1 system_u:object_r:NetworkManager_dispatcher_dhclient_script_t:s0
      1 system_u:object_r:named_exec_t:s0
      1 system_u:object_r:modemmanager_unit_file_t:s0
      1 system_u:object_r:modemmanager_exec_t:s0
      1 system_u:object_r:mock_exec_t:s0
      1 system_u:object_r:mcelog_exec_t:s0
      1 system_u:object_r:mandb_exec_t:s0
      1 system_u:object_r:logrotate_exec_t:s0
      1 system_u:object_r:login_exec_t:s0
      1 system_u:object_r:lockdev_exec_t:s0
      1 system_u:object_r:locate_exec_t:s0
      1 system_u:object_r:load_policy_exec_t:s0
      1 system_u:object_r:loadkeys_exec_t:s0
      1 system_u:object_r:ld_so_t:s0
      1 system_u:object_r:ldconfig_exec_t:s0
      1 system_u:object_r:kmod_exec_t:s0
      1 system_u:object_r:keyutils_dns_resolver_exec_t:s0
      1 system_u:object_r:kdump_exec_t:s0
      1 system_u:object_r:journalctl_exec_t:s0
      1 system_u:object_r:install_exec_t:s0
      1 system_u:object_r:iiosensorproxy_exec_t:s0
      1 system_u:object_r:hypervvssd_unit_file_t:s0
      1 system_u:object_r:hypervkvp_exec_t:s0
      1 system_u:object_r:hwclock_exec_t:s0
      1 system_u:object_r:httpd_sys_script_exec_t:s0
      1 system_u:object_r:httpd_suexec_exec_t:s0
      1 system_u:object_r:httpd_rotatelogs_exec_t:s0
      1 system_u:object_r:httpd_passwd_exec_t:s0
      1 system_u:object_r:hostname_exec_t:s0
      1 system_u:object_r:gssproxy_unit_file_t:s0
      1 system_u:object_r:gssproxy_exec_t:s0
      1 system_u:object_r:gssd_exec_t:s0
      1 system_u:object_r:gpg_agent_exec_t:s0
      1 system_u:object_r:gnome_remote_desktop_exec_t:s0
      1 system_u:object_r:glusterd_exec_t:s0
      1 system_u:object_r:gkeyringd_exec_t:s0
      1 system_u:object_r:getty_exec_t:s0
      1 system_u:object_r:geoclue_exec_t:s0
      1 system_u:object_r:fprintd_exec_t:s0
      1 system_u:object_r:flatpak_helper_exec_t:s0
      1 system_u:object_r:firewalld_unit_file_t:s0
      1 system_u:object_r:firewalld_exec_t:s0
      1 system_u:object_r:fedoratp_exec_t:s0
      1 system_u:object_r:dnsmasq_unit_file_t:s0
      1 system_u:object_r:dnsmasq_exec_t:s0
      1 system_u:object_r:dmesg_exec_t:s0
      1 system_u:object_r:devicekit_power_exec_t:s0
      1 system_u:object_r:devicekit_exec_t:s0
      1 system_u:object_r:devicekit_disk_exec_t:s0
      1 system_u:object_r:cups_brf_exec_t:s0
      1 system_u:object_r:consolehelper_exec_t:s0
      1 system_u:object_r:conmon_exec_t:s0
      1 system_u:object_r:colord_unit_file_t:s0
      1 system_u:object_r:colord_exec_t:s0
      1 system_u:object_r:cockpit_session_exec_t:s0
      1 system_u:object_r:chronyd_exec_t:s0
      1 system_u:object_r:chronyc_exec_t:s0
      1 system_u:object_r:chkpwd_exec_t:s0
      1 system_u:object_r:checkpolicy_exec_t:s0
      1 system_u:object_r:brltty_unit_file_t:s0
      1 system_u:object_r:brltty_exec_t:s0
      1 system_u:object_r:boltd_exec_t:s0
      1 system_u:object_r:bluetooth_exec_t:s0
      1 system_u:object_r:blkmapd_exec_t:s0
      1 system_u:object_r:avahi_exec_t:s0
      1 system_u:object_r:auditd_unit_file_t:s0
      1 system_u:object_r:auditd_exec_t:s0
      1 system_u:object_r:auditctl_exec_t:s0
      1 system_u:object_r:anaconda_generator_exec_t:s0
      1 system_u:object_r:acct_exec_t:s0
      1 system_u:object_r:accountsd_exec_t:s0
      1 system_u:object_r:abrt_watch_log_exec_t:s0

(OTOH, I think that the policy could be simplified in many cases. For example, I see abrt_unit_file_t used for /usr/lib/systemd/system/abrt-*.service, and abrt_unit_file_t:file manage_file_perms in the policy. I would guess that those file were originally in /etc, and then they were moved. Nothing at runtime should modify those files under /usr… But that’s not directly related to the topic at hand.)

We have a long long tail of file contexts used for a single unit file. I guess that they were added because systemd then uses this to set context for the unit, and selinux policy can control operations on the unit. But unit files are unique, so this will not cause a problem for linking.

There’s an equivalence set for /etcc/systemd/system to /usr/lib/systemd/system, see “SELinux Distribution fcontext Equivalence” part of semanage fcontext -l

# matchpathcon /usr/lib/systemd/system/abrt-vmcore.service /etc/systemd/system/abrt-vmcore.service
/usr/lib/systemd/system/abrt-vmcore.service     system_u:object_r:abrt_unit_file_t:s0
/etc/systemd/system/abrt-vmcore.service system_u:object_r:abrt_unit_file_t:s0

As for the labels, it would help to understand what kind of files are target of this change. Hardlinks and SELinux can do unexpected things based on order how files are labeled:

#cd /usr

# touch bin/file1

# ln bin/file1 lib64/file2

# ls -Z bin/file1 lib64/file2
system_u:object_r:bin_t:s0 bin/file1  system_u:object_r:bin_t:s0 lib64/file2

# restorecon -F -v lib64/file2
Relabeled /usr/lib64/file2 from system_u:object_r:bin_t:s0 to system_u:object_r:lib_t:s0

# ls -Z bin/file1 lib64/file2
system_u:object_r:lib_t:s0 bin/file1  system_u:object_r:lib_t:s0 lib64/file2
1 Like

Hi, won’t this increase system breakages?

=> In case of a malware attack, it’d be easier for the malware to take down the system, since it only has to infect one set of files. And then it has basically infected a lot more packages at once.

Inspired by your find command I made a little script to look for files that hardlink might consider, but that had different contexts. There are actually some examples. E.g.

-rw-r--r--. 1 root root system_u:object_r:lib_t:SystemLow 126 16 jan  2025 /usr/lib64/R/library/xfun/Meta/features.rds
-rw-r--r--. 1 root root system_u:object_r:usr_t:SystemLow 126 16 jan  2025 /usr/share/R/library/evaluate/Meta/features.rds

(Though those are not from the same package. I’ll modify my script to check for that too a little later.)

Hi, won’t this increase system breakages?

=> In case of a malware attack, it’d be easier for the malware to take down the system, since it only has to infect one set of files. And then it has basically infected a lot more packages at once.

No, this doesn’t make sense.

Only files with the same ownership and permissions will be hardlinked, so there are few cases (a privilege-escalation vulnerability that allows only a certain path to be improperly overwritten?) where hypothetical malware might be able to modify one copy of a file but not the other. Where access isn’t an obstacle, doing the same thing many times instead of once is not something that computer programs struggle with.

Even if one accepted the overall premise of the concern, this proposal is only for hardlinking files within the same package, so it cannot make it easier to “infect a lot more packages at once.”

1 Like

Thanks for confirming that.

I extended my script and reran it on a host I’m using. It did found a number of cases where a single package contains the same file in more than one place with different contexts. For example

  1. python-rdps-py contains
    1.1 /usr/share/licenses/python3-rpds-py/LICENSES.dependencies with type usr_t
    1.2 /usr/lib64/python3.13/site-packages/rpds_py-0.23.1.dist-info/licenses/LICENSES.dependencies with type lib_t
  2. firefox contains
    2.1. /usr/share/icons/hicolor/16x16/apps/firefox.png with type usr_t
    2.2 /usr/lib64/firefox/browser/chrome/icons/default/default16.png with type lib_t
  3. gimp contains
    3.1 /usr/share/locale/id/LC_MESSAGES/gimp30-std-plug-ins.mo with type locale_t
    3.2 /usr/lib64/gimp/3.0/extensions/org.gimp.extension.goat-exercises/locale/id/LC_MESSAGES/org.gimp.extension.goat-exercises.mo with type lib_t

In total the script found 213 occurrences on this system. While there might be some false positives in there, my script is not perfect, it shows the problem is not only hypothetical. While I like this proposal, I do think this aspect needs to be considered when implementing.

In total the script found 213 occurrences on this system

Please post the full list somewhere. We’ll need to keep an eye on this to check for any regressions.

Ok, sure, I’ll just clean it up a little to make it understandable without a long explanation. :slight_smile:

This change proposal has now been submitted to FESCo with ticket #3460 for voting.

To find out more, please visit our Changes Policy documentation.

After trying a little to reformat the output of my bash script, I decided to rewrite the thing in python instead and produce an YAML result.

This version finds 208 problematic cases. It probably excluded a few false positives. Obviously, on a different host the number could be very different depending on what is installed.

The script itself, if anyone is curious exactly what I did, can be found on the side.