may_linkat(9)

πŸ“¦ version 7.3.1, in nixpkgs unstable

πŸ’… may_linkat(9)

ok so bestie, we're in the Kernel API Manual today and this page is all about may_linkat β€” the little permission bouncer standing at the door of hardlink creation. Let's get into it ✨


πŸŽ€ NAME πŸŽ€

may_linkat β€” the vibe check for creating a hardlink πŸ”—


🌸 SYNOPSIS 🌸

int may_linkat (struct mnt_idmap *idmap , const struct path *link );

πŸ¦‹ ARGUMENTS πŸ¦‹

  • idmap β€” the idmap of the mount that the inode was found from. yeah, it matters, keep reading πŸ‘€
  • link β€” the source you're trying to hardlink from

πŸ’– DESCRIPTION πŸ’–

so here's the tea: this function blocks the hardlink when all of these are true at once:

  • sysctl_protected_hardlinks is enabled βœ…
  • fsuid does not match the inode πŸ™…β€β™€οΈ
  • the hardlink source is unsafe (peep safe_hardlink_source above for that whole situation)
  • no CAP_FOWNER in a namespace with the inode owner uid mapped

all four, together, at the same time. that's the combo that gets you blocked, no cap πŸ’€


now, if the inode was found through an idmapped mount, you literally have to pass the idmap of the vfsmount through idmap. bestie this is not optional. the function will then take care of mapping the inode according to idmap before it does any permission checking 🫢

on non-idmapped mounts, or if the permission checking is supposed to happen on the raw inode, just pass nop_mnt_idmap and you're golden ✨

returns 0 if successful, -ve on error. slay, simple.

ngl the whole point is: don't let randos hardlink to files they don't own. it's giving security, it's giving protected hardlinks, it's giving main character energy πŸ›‘οΈ

πŸ”₯ SEE ALSO πŸ”₯

Kernel file ./fs/namei.c

check_acl(9), acl_permission_check(9), generic_permission(9), do_inode_permission(9), sb_permission(9), inode_permission(9), path_get(9), path_put(9), path_connected(9), try_to_unlazy(9), try_to_unlazy_next(9), complete_walk(9), may_follow_link(9), safe_hardlink_source(9), may_create_in_sticky(9), lookup_fast(9), start_dirop(9), end_dirop(9), kern_path_parent(9), vfs_path_parent_lookup(9), vfs_path_lookup(9), try_lookup_noperm(9), lookup_noperm(9), lookup_one(9), lookup_one_unlocked(9), lookup_one_positive_killable(9), lookup_one_positive_unlocked(9), lookup_noperm_unlocked(9), start_creating(9), start_removing(9), start_creating_killable(9), start_removing_killable(9), start_creating_noperm(9), start_removing_noperm(9), start_creating_dentry(9), start_removing_dentry(9), __start_renaming(9), start_renaming(9), start_renaming_dentry(9), start_renaming_two_dentries(9), vfs_prepare_mode(9), vfs_create(9), atomic_open(9), vfs_lookup_open(9), vfs_tmpfile(9), kernel_tmpfile_open(9), end_creating_path(9), dentry_create(9), vfs_mknod(9), vfs_mkdir(9), vfs_rmdir(9), vfs_unlink(9), vfs_symlink(9), vfs_link(9), vfs_rename(9), vfs_readlink(9), vfs_get_link(9), page_get_link(9), page_put_link(9)

(yeah that's a lot of links. iykyk. go read them all, they're iconic πŸ‘‘)


that's the whole page, bestie! πŸŽ€ this beauty lives in the ./fs source tree and it's dated October 2026 β€” catch you in the kernel, mwah πŸ’œ

πŸ“¦ where this page comes from