* [PATCH v4 34/34] lsm: add a LSM_STARTED_ALL notification event
From: Paul Moore @ 2025-09-16 22:04 UTC (permalink / raw)
To: linux-security-module, linux-integrity, selinux
Cc: John Johansen, Mimi Zohar, Roberto Sassu, Fan Wu,
Mickaël Salaün, Günther Noack, Kees Cook,
Micah Morton, Casey Schaufler, Tetsuo Handa, Nicolas Bouchinet,
Xiu Jianfeng
In-Reply-To: <20250916220355.252592-36-paul@paul-moore.com>
Add a new LSM notifier event, LSM_STARTED_ALL, which is fired once at
boot when all of the LSMs have been started.
Reviewed-by: Kees Cook <kees@kernel.org>
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: John Johansen <john.johhansen@canonical.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
include/linux/security.h | 1 +
security/lsm_init.c | 1 +
2 files changed, 2 insertions(+)
diff --git a/include/linux/security.h b/include/linux/security.h
index 8560c50edd2e..c13f0a849024 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -85,6 +85,7 @@ struct timezone;
enum lsm_event {
LSM_POLICY_CHANGE,
+ LSM_STARTED_ALL,
};
struct dm_verity_digest {
diff --git a/security/lsm_init.c b/security/lsm_init.c
index 2bd705836df8..af4046c5c581 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -556,6 +556,7 @@ static int __init security_initcall_late(void)
rc = lsm_initcall(late);
lsm_pr_dbg("all enabled LSMs fully activated\n");
+ call_blocking_lsm_notifier(LSM_STARTED_ALL, NULL);
return rc;
}
--
2.51.0
^ permalink raw reply related
* Re: [PATCH v2 0/7] fs/9p: Reuse inode based on path (in addition to qid)
From: Tingmao Wang @ 2025-09-16 23:59 UTC (permalink / raw)
To: Christian Schoenebeck, Dominique Martinet
Cc: Mickaël Salaün, Eric Van Hensbergen, Latchesar Ionkov,
v9fs, Günther Noack, linux-security-module, Jan Kara,
Amir Goldstein, Matthew Bobrowski, Al Viro, Christian Brauner,
linux-fsdevel
In-Reply-To: <3070012.VW4agfvzBM@silver>
On 9/16/25 20:22, Christian Schoenebeck wrote:
> On Tuesday, September 16, 2025 4:01:40 PM CEST Tingmao Wang wrote:
>> On 9/16/25 14:35, Dominique Martinet wrote:
>>> Tingmao Wang wrote on Tue, Sep 16, 2025 at 01:44:27PM +0100:
>>>> [...]
>>>>
>>>> Note that in discussion with Mickaël (maintainer of Landlock) he
>>>> indicated
>>>> that he would be comfortable for Landlock to track a qid, instead of
>>>> holding a inode, specifically for 9pfs.
>>>
>>> Yes, I saw that, but what you pointed out about qid reuse make me
>>> somewhat uncomfortable with that direction -- you could allow a
>>> directory, delete it, create a new one somewhere else and if the
>>> underlying fs reuse the same inode number the rule would allow an
>>> intended directory instead so I'd rather not rely on qid for this
>>> either.
>>> But if you think that's not a problem in practice (because e.g. landlock
>>> would somehow detect the dir got deleted or another good reason it's not
>>> a problem) then I agree it's probably the simplest way forward
>>> implementation-wise.
>>
>> Sorry, I forgot to add that this idea would also involve Landlock holding
>> a reference to the fid (or dentry, but that's problematic due to breaking
>> unmount unless we can have a new hook) to keep the file open on the host
>> side so that the qid won't be reused (ignoring collisions caused by
>> different filesystems mounted under one 9pfs export when multidev mapping
>> is not enabled)
>
> I see that you are proposing an option for your proposed qid based re-using of
> dentries. I don't think it should be on by default though, considering what we
> already discussed (e.g. inodes recycled by ext4, but also not all 9p servers
> handling inode collisions).
Just to be clear, this approach (Landlock holding a fid reference, then
using the qid as a key to search for rules when a Landlocked process
accesses the previously remembered file, possibly after the file has been
moved on the server) would only be in Landlock, and would only affect
Landlock, not 9pfs (so not sure what you meant by "re-using of dentries").
The idea behind holding a fid reference within Landlock is that, because
we have the file open, the inode would not get recycled in ext4, and thus
no other file will reuse the qid, until we close that reference (when the
Landlock domain terminates, or when the 9p filesystem is unmounted)
>
>> (There's the separate issue of QEMU not seemingly keeping a directory open
>> on the host when the guest has a fid to it tho. I checked that if the dir
>> is renamed on the host side, any process in the guest that has a fd to it
>> (checked via cd in a shell) will not be able to use that fd to read it
>> anymore. This also means that another directory might be created with the
>> same qid.path)
>
> For all open FIDs QEMU retains a descriptor to the file/directory.
>
> Which 9p message do you see sent to server, Trename or Trenameat?
>
> Does this always happen to you or just sometimes, i.e. under heavy load?
Always happen, see log: (no Trename since the rename is done on the host)
qemu flags: -virtfs "local,path=/tmp/test,mount_tag=test,security_model=passthrough,readonly=off,multidevs=remap"
qemu version: QEMU emulator version 10.1.0 (Debian 1:10.1.0+ds-5)
guest kernel version: 6.17.0-rc5 (for the avoidance of doubt, this is clean 6.17-rc5 with no patches)
qemu pid: 511476
root@host # ls -la /proc/511476/fd | grep test
lr-x------ 1 root root 64 Sep 17 00:35 41 -> /tmp/test
root@guest # mount --mkdir -t 9p -o trans=virtio,cache=none,inodeident=qid,debug=13 test /tmp/test
root@guest # mkdir /tmp/test/dir1
root@guest # cd /tmp/test/dir1
9pnet: -- v9fs_vfs_getattr_dotl (183): dentry: ffff888102ed4d38
9pnet: -- v9fs_fid_find (183): dentry: / (ffff888102ed4d38) uid 0 any 0
9pnet: (00000183) >>> TGETATTR fid 1, request_mask 16383
9pnet: (00000183) >>> size=19 type: 24 tag: 0
9pnet: (00000183) <<< size=160 type: 25 tag: 0
9pnet: (00000183) <<< RGETATTR st_result_mask=6143
<<< qid=80.3.68c9c8a3
<<< st_mode=000043ff st_nlink=3
<<< st_uid=0 st_gid=0
<<< st_rdev=0 st_size=3c st_blksize=131072 st_blocks=0
<<< st_atime_sec=1758065706 st_atime_nsec=857221735
<<< st_mtime_sec=1758065827 st_mtime_nsec=745359877
<<< st_ctime_sec=1758065827 st_ctime_nsec=745359877
<<< st_btime_sec=0 st_btime_nsec=0
<<< st_gen=0 st_data_version=0
9pnet: -- v9fs_vfs_lookup (183): dir: ffff8881090e0000 dentry: (dir1) ffff888102e458f8 flags: 1
9pnet: -- v9fs_fid_find (183): dentry: / (ffff888102ed4d38) uid 0 any 0
9pnet: (00000183) >>> TWALK fids 1,2 nwname 1d wname[0] dir1
9pnet: (00000183) >>> size=23 type: 110 tag: 0
9pnet: (00000183) <<< size=22 type: 111 tag: 0
9pnet: (00000183) <<< RWALK nwqid 1:
9pnet: (00000183) <<< [0] 80.5.68c9dca3
9pnet: (00000183) >>> TGETATTR fid 2, request_mask 6143
9pnet: (00000183) >>> size=19 type: 24 tag: 0
9pnet: (00000183) <<< size=160 type: 25 tag: 0
9pnet: (00000183) <<< RGETATTR st_result_mask=6143
<<< qid=80.5.68c9dca3
<<< st_mode=000041ed st_nlink=2
<<< st_uid=0 st_gid=0
<<< st_rdev=0 st_size=28 st_blksize=131072 st_blocks=0
<<< st_atime_sec=1758065827 st_atime_nsec=745359877
<<< st_mtime_sec=1758065827 st_mtime_nsec=745359877
<<< st_ctime_sec=1758065827 st_ctime_nsec=749830521
<<< st_btime_sec=0 st_btime_nsec=0
<<< st_gen=0 st_data_version=0
9pnet: -- v9fs_vfs_getattr_dotl (183): dentry: ffff888102e458f8
9pnet: -- v9fs_fid_find (183): dentry: dir1 (ffff888102e458f8) uid 0 any 0
9pnet: (00000183) >>> TGETATTR fid 2, request_mask 16383
9pnet: (00000183) >>> size=19 type: 24 tag: 0
9pnet: (00000183) <<< size=160 type: 25 tag: 0
9pnet: (00000183) <<< RGETATTR st_result_mask=6143
<<< qid=80.5.68c9dca3
<<< st_mode=000041ed st_nlink=2
<<< st_uid=0 st_gid=0
<<< st_rdev=0 st_size=28 st_blksize=131072 st_blocks=0
<<< st_atime_sec=1758065827 st_atime_nsec=745359877
<<< st_mtime_sec=1758065827 st_mtime_nsec=745359877
<<< st_ctime_sec=1758065827 st_ctime_nsec=749830521
<<< st_btime_sec=0 st_btime_nsec=0
<<< st_gen=0 st_data_version=0
9pnet: -- v9fs_dentry_release (183): dentry: dir1 (ffff888102e458f8)
9pnet: (00000183) >>> TCLUNK fid 2 (try 0)
9pnet: (00000183) >>> size=11 type: 120 tag: 0
9pnet: (00000183) <<< size=7 type: 121 tag: 0
9pnet: (00000183) <<< RCLUNK fid 2
9pnet: -- v9fs_vfs_lookup (183): dir: ffff8881090e0000 dentry: (dir1) ffff888102e45a70 flags: 3
9pnet: -- v9fs_fid_find (183): dentry: / (ffff888102ed4d38) uid 0 any 0
9pnet: (00000183) >>> TWALK fids 1,2 nwname 1d wname[0] dir1
9pnet: (00000183) >>> size=23 type: 110 tag: 0
9pnet: (00000183) <<< size=22 type: 111 tag: 0
9pnet: (00000183) <<< RWALK nwqid 1:
9pnet: (00000183) <<< [0] 80.5.68c9dca3
9pnet: (00000183) >>> TGETATTR fid 2, request_mask 6143
9pnet: (00000183) >>> size=19 type: 24 tag: 0
9pnet: (00000183) <<< size=160 type: 25 tag: 0
9pnet: (00000183) <<< RGETATTR st_result_mask=6143
<<< qid=80.5.68c9dca3
<<< st_mode=000041ed st_nlink=2
<<< st_uid=0 st_gid=0
<<< st_rdev=0 st_size=28 st_blksize=131072 st_blocks=0
<<< st_atime_sec=1758065827 st_atime_nsec=745359877
<<< st_mtime_sec=1758065827 st_mtime_nsec=745359877
<<< st_ctime_sec=1758065827 st_ctime_nsec=749830521
<<< st_btime_sec=0 st_btime_nsec=0
<<< st_gen=0 st_data_version=0
(fid 2 is now a persistent handle pointing to /dir1, not sure why the
walk was done twice)
root@host # ls -la /proc/511476/fd | grep test
lr-x------ 1 root root 64 Sep 17 00:35 41 -> /tmp/test
(no fd points to dir1)
root@host # mv -v /tmp/test/dir1 /tmp/test/dir2
renamed '/tmp/test/dir1' -> '/tmp/test/dir2'
root@guest:/tmp/test/dir1# ls
9pnet: -- v9fs_vfs_getattr_dotl (183): dentry: ffff888102e45a70
9pnet: -- v9fs_fid_find (183): dentry: dir1 (ffff888102e45a70) uid 0 any 0
9pnet: (00000183) >>> TGETATTR fid 2, request_mask 16383
9pnet: (00000183) >>> size=19 type: 24 tag: 0
9pnet: (00000183) <<< size=11 type: 7 tag: 0
9pnet: (00000183) <<< RLERROR (-2)
9pnet: -- v9fs_file_open (188): inode: ffff888102e80640 file: ffff88810af45340
9pnet: -- v9fs_fid_find (188): dentry: dir1 (ffff888102e45a70) uid 0 any 0
9pnet: (00000188) >>> TWALK fids 2,3 nwname 0d wname[0] (null)
9pnet: (00000188) >>> size=17 type: 110 tag: 0
9pnet: (00000188) <<< size=11 type: 7 tag: 0
9pnet: (00000188) <<< RLERROR (-2)
ls: cannot open directory '.': No such file or directory
It looks like as soon as the directory was moved on the host, TGETATTR on
the guest-opened fid 2 fails, even though I would expect that if QEMU
opens a fd to the dir and use that fd whenever fid 2 is used, that
TGETATTR should succeed. The fact that I can't see anything pointing to
dir1 in /proc/511476/fd was also suspicious.
Also, if I remove the dir on the host, then repoen it in the guest, ls
starts working again:
root@host # mv -v /tmp/test/dir2 /tmp/test/dir1
renamed '/tmp/test/dir2' -> '/tmp/test/dir1'
root@guest:/tmp/test/dir1# ls
9pnet: -- v9fs_file_open (189): inode: ffff888102e80640 file: ffff88810af47100
9pnet: -- v9fs_fid_find (189): dentry: dir1 (ffff888102e45a70) uid 0 any 0
9pnet: (00000189) >>> TWALK fids 2,3 nwname 0d wname[0] (null)
9pnet: (00000189) >>> size=17 type: 110 tag: 0
9pnet: (00000189) <<< size=9 type: 111 tag: 0
9pnet: (00000189) <<< RWALK nwqid 0:
9pnet: (00000189) >>> TLOPEN fid 3 mode 100352
9pnet: (00000189) >>> size=15 type: 12 tag: 0
9pnet: (00000189) <<< size=24 type: 13 tag: 0
9pnet: (00000189) <<< RLOPEN qid 80.5.68c9dca3 iounit 0
9pnet: -- v9fs_vfs_getattr_dotl (189): dentry: ffff888102e45a70
9pnet: -- v9fs_fid_find (189): dentry: dir1 (ffff888102e45a70) uid 0 any 0
9pnet: (00000189) >>> TGETATTR fid 2, request_mask 16383
9pnet: (00000189) >>> size=19 type: 24 tag: 0
9pnet: (00000189) <<< size=160 type: 25 tag: 0
9pnet: (00000189) <<< RGETATTR st_result_mask=6143
<<< qid=80.5.68c9dca3
<<< st_mode=000041ed st_nlink=2
<<< st_uid=0 st_gid=0
<<< st_rdev=0 st_size=28 st_blksize=131072 st_blocks=0
<<< st_atime_sec=1758065827 st_atime_nsec=745359877
<<< st_mtime_sec=1758065827 st_mtime_nsec=745359877
<<< st_ctime_sec=1758066075 st_ctime_nsec=497687251
<<< st_btime_sec=0 st_btime_nsec=0
<<< st_gen=0 st_data_version=0
9pnet: -- v9fs_dir_readdir_dotl (189): name dir1
9pnet: (00000189) >>> TREADDIR fid 3 offset 0 count 131072
9pnet: (00000189) >>> size=23 type: 40 tag: 0
9pnet: (00000189) <<< size=62 type: 41 tag: 0
9pnet: (00000189) <<< RREADDIR count 51
9pnet: (00000189) >>> TREADDIR fid 3 offset 2147483647 count 131072
9pnet: (00000189) >>> size=23 type: 40 tag: 0
9pnet: (00000189) <<< size=11 type: 41 tag: 0
9pnet: (00000189) <<< RREADDIR count 0
9pnet: -- v9fs_dir_readdir_dotl (189): name dir1
9pnet: (00000189) >>> TREADDIR fid 3 offset 2147483647 count 131072
9pnet: (00000189) >>> size=23 type: 40 tag: 0
9pnet: (00000189) <<< size=11 type: 41 tag: 0
9pnet: (00000189) <<< RREADDIR count 0
9pnet: -- v9fs_dir_release (189): inode: ffff888102e80640 filp: ffff88810af47100 fid: 3
9pnet: (00000189) >>> TCLUNK fid 3 (try 0)
9pnet: (00000189) >>> size=11 type: 120 tag: 0
9pnet: (00000189) <<< size=7 type: 121 tag
root@guest:/tmp/test/dir1# echo $?
0
Somehow if I rename in the guest, it all works, even though it's using the
same fid 2 (and it didn't ask QEMU to walk the new path)
root@guest:/tmp/test/dir1# mv /tmp/test/dir1 /tmp/test/dir2
9pnet: -- v9fs_vfs_getattr_dotl (183): dentry: ffff888102e45a70
9pnet: -- v9fs_fid_find (183): dentry: dir1 (ffff888102e45a70) uid 0 any 0
9pnet: (00000183) >>> TGETATTR fid 2, request_mask 16383
9pnet: (00000183) >>> size=19 type: 24 tag: 0
9pnet: (00000183) <<< size=160 type: 25 tag: 0
9pnet: (00000183) <<< RGETATTR st_result_mask=6143
<<< qid=80.5.68c9dca3
<<< st_mode=000041ed st_nlink=2
<<< st_uid=0 st_gid=0
<<< st_rdev=0 st_size=28 st_blksize=131072 st_blocks=0
<<< st_atime_sec=1758066561 st_atime_nsec=442431580
<<< st_mtime_sec=1758065827 st_mtime_nsec=745359877
<<< st_ctime_sec=1758066559 st_ctime_nsec=570428555
<<< st_btime_sec=0 st_btime_nsec=0
<<< st_gen=0 st_data_version=0
9pnet: -- v9fs_vfs_lookup (194): dir: ffff8881090e0000 dentry: (dir2) ffff888102edca48 flags: e0000
9pnet: -- v9fs_fid_find (194): dentry: / (ffff888102ed4d38) uid 0 any 0
9pnet: (00000194) >>> TWALK fids 1,3 nwname 1d wname[0] dir2
9pnet: (00000194) >>> size=23 type: 110 tag: 0
9pnet: (00000194) <<< size=11 type: 7 tag: 0
9pnet: (00000194) <<< RLERROR (-2)
9pnet: -- v9fs_dentry_release (194): dentry: dir2 (ffff888102edca48)
9pnet: -- v9fs_vfs_lookup (194): dir: ffff8881090e0000 dentry: (dir2) ffff888102edcbc0 flags: 0
9pnet: -- v9fs_fid_find (194): dentry: / (ffff888102ed4d38) uid 0 any 0
9pnet: (00000194) >>> TWALK fids 1,3 nwname 1d wname[0] dir2
9pnet: (00000194) >>> size=23 type: 110 tag: 0
9pnet: (00000194) <<< size=11 type: 7 tag: 0
9pnet: (00000194) <<< RLERROR (-2)
9pnet: -- v9fs_dentry_release (194): dentry: dir2 (ffff888102edcbc0)
9pnet: -- v9fs_vfs_lookup (194): dir: ffff8881090e0000 dentry: (dir2) ffff888102edcd38 flags: a0000
9pnet: -- v9fs_fid_find (194): dentry: / (ffff888102ed4d38) uid 0 any 0
9pnet: (00000194) >>> TWALK fids 1,3 nwname 1d wname[0] dir2
9pnet: (00000194) >>> size=23 type: 110 tag: 0
9pnet: (00000194) <<< size=11 type: 7 tag: 0
9pnet: (00000194) <<< RLERROR (-2)
9pnet: -- v9fs_vfs_rename (194):
9pnet: -- v9fs_fid_find (194): dentry: dir1 (ffff888102e45a70) uid 0 any 0
9pnet: -- v9fs_fid_find (194): dentry: / (ffff888102ed4d38) uid 0 any 0
9pnet: (00000194) >>> TWALK fids 1,3 nwname 0d wname[0] (null)
9pnet: (00000194) >>> size=17 type: 110 tag: 0
9pnet: (00000194) <<< size=9 type: 111 tag: 0
9pnet: (00000194) <<< RWALK nwqid 0:
9pnet: -- v9fs_fid_find (194): dentry: / (ffff888102ed4d38) uid 0 any 0
9pnet: (00000194) >>> TWALK fids 1,4 nwname 0d wname[0] (null)
9pnet: (00000194) >>> size=17 type: 110 tag: 0
9pnet: (00000194) <<< size=9 type: 111 tag: 0
9pnet: (00000194) <<< RWALK nwqid 0:
9pnet: (00000194) >>> TRENAMEAT olddirfid 3 old name dir1 newdirfid 4 new name dir2
9pnet: (00000194) >>> size=27 type: 74 tag: 0
9pnet: (00000194) <<< size=7 type: 75 tag: 0
9pnet: (00000194) <<< RRENAMEAT newdirfid 4 new name dir2
9pnet: (00000194) >>> TCLUNK fid 4 (try 0)
9pnet: (00000194) >>> size=11 type: 120 tag: 0
9pnet: (00000194) <<< size=7 type: 121 tag: 0
9pnet: (00000194) <<< RCLUNK fid 4
9pnet: (00000194) >>> TCLUNK fid 3 (try 0)
9pnet: (00000194) >>> size=11 type: 120 tag: 0
9pnet: (00000194) <<< size=7 type: 121 tag: 0
9pnet: (00000194) <<< RCLUNK fid 3
9pnet: -- v9fs_dentry_release (194): dentry: dir2 (ffff888102edcd38)
root@guest:/tmp/test/dir1# ls
9pnet: -- v9fs_file_open (195): inode: ffff888102e80640 file: ffff88810b2b1500
9pnet: -- v9fs_fid_find (195): dentry: dir2 (ffff888102e45a70) uid 0 any 0
9pnet: (00000195) >>> TWALK fids 2,3 nwname 0d wname[0] (null)
9pnet: (00000195) >>> size=17 type: 110 tag: 0
9pnet: (00000195) <<< size=9 type: 111 tag: 0
9pnet: (00000195) <<< RWALK nwqid 0:
9pnet: (00000195) >>> TLOPEN fid 3 mode 100352
9pnet: (00000195) >>> size=15 type: 12 tag: 0
9pnet: (00000195) <<< size=24 type: 13 tag: 0
9pnet: (00000195) <<< RLOPEN qid 80.5.68c9dca3 iounit 0
9pnet: -- v9fs_vfs_getattr_dotl (195): dentry: ffff888102e45a70
9pnet: -- v9fs_fid_find (195): dentry: dir2 (ffff888102e45a70) uid 0 any 0
9pnet: (00000195) >>> TGETATTR fid 2, request_mask 16383
9pnet: (00000195) >>> size=19 type: 24 tag: 0
9pnet: (00000195) <<< size=160 type: 25 tag: 0
9pnet: (00000195) <<< RGETATTR st_result_mask=6143
<<< qid=80.5.68c9dca3
<<< st_mode=000041ed st_nlink=2
<<< st_uid=0 st_gid=0
<<< st_rdev=0 st_size=28 st_blksize=131072 st_blocks=0
<<< st_atime_sec=1758066561 st_atime_nsec=442431580
<<< st_mtime_sec=1758065827 st_mtime_nsec=745359877
<<< st_ctime_sec=1758066568 st_ctime_nsec=562443096
<<< st_btime_sec=0 st_btime_nsec=0
<<< st_gen=0 st_data_version=0
9pnet: -- v9fs_dir_readdir_dotl (195): name dir2
9pnet: (00000195) >>> TREADDIR fid 3 offset 0 count 131072
9pnet: (00000195) >>> size=23 type: 40 tag: 0
9pnet: (00000195) <<< size=62 type: 41 tag: 0
9pnet: (00000195) <<< RREADDIR count 51
9pnet: (00000195) >>> TREADDIR fid 3 offset 2147483647 count 131072
9pnet: (00000195) >>> size=23 type: 40 tag: 0
9pnet: (00000195) <<< size=11 type: 41 tag: 0
9pnet: (00000195) <<< RREADDIR count 0
9pnet: -- v9fs_dir_readdir_dotl (195): name dir2
9pnet: (00000195) >>> TREADDIR fid 3 offset 2147483647 count 131072
9pnet: (00000195) >>> size=23 type: 40 tag: 0
9pnet: (00000195) <<< size=11 type: 41 tag: 0
9pnet: (00000195) <<< RREADDIR count 0
9pnet: -- v9fs_dir_release (195): inode: ffff888102e80640 filp: ffff88810b2b1500 fid: 3
9pnet: (00000195) >>> TCLUNK fid 3 (try 0)
9pnet: (00000195) >>> size=11 type: 120 tag: 0
9pnet: (00000195) <<< size=7 type: 121 tag: 0
9pnet: (00000195) <<< RCLUNK fid 3
If this is surprising, I'm happy to take a deeper look over the weekend
(but I've never tried to debug QEMU itself :D)
> Because even though QEMU retains descriptors of open FIDs; when the QEMU
> process approaches host system's max. allowed number of open file descriptors
> then v9fs_reclaim_fd() [hw/9pfs/9p.c] is called, which closes some descriptors
> of older FIDs to (at least) keep the QEMU process alive.
>
> BTW: to prevent these descriptor reclaims to happen too often, I plan to do
> what many other files servers do: asking the host system on process start to
> increase the max. number of file descriptors.
Note that the above is reproduced with only 1 file open (the dir being
renamed around)
Kind regards,
Tingmao
>
> /Christian
>
>
^ permalink raw reply
* Re: [PATCH] memfd,selinux: call security_inode_init_security_anon
From: Thiébaud Weksteen @ 2025-09-17 0:34 UTC (permalink / raw)
To: Paul Moore, Hugh Dickins
Cc: James Morris, Stephen Smalley, Jeff Vander Stoep, Nick Kralevich,
Jeff Xu, Baolin Wang, linux-kernel, linux-security-module,
selinux, linux-mm
In-Reply-To: <CAHC9VhQejS05C8AExkh32GidBdzXjauKPP6T_3eSGZDEwfwDuA@mail.gmail.com>
On Wed, Sep 17, 2025 at 1:26 AM Paul Moore <paul@paul-moore.com> wrote:
>
> On Tue, Sep 16, 2025 at 1:07 AM Hugh Dickins <hughd@google.com> wrote:
> > On Wed, 3 Sep 2025, Paul Moore wrote:
> > > On Aug 25, 2025 "=?UTF-8?q?Thi=C3=A9baud=20Weksteen?=" <tweek@google.com> wrote:
> > > >
> > > > Prior to this change, no security hooks were called at the creation of a
> > > > memfd file. It means that, for SELinux as an example, it will receive
> > > > the default type of the filesystem that backs the in-memory inode. In
> > > > most cases, that would be tmpfs, but if MFD_HUGETLB is passed, it will
> > > > be hugetlbfs. Both can be considered implementation details of memfd.
> > > >
> > > > It also means that it is not possible to differentiate between a file
> > > > coming from memfd_create and a file coming from a standard tmpfs mount
> > > > point.
> > > >
> > > > Additionally, no permission is validated at creation, which differs from
> > > > the similar memfd_secret syscall.
> > > >
> > > > Call security_inode_init_security_anon during creation. This ensures
> > > > that the file is setup similarly to other anonymous inodes. On SELinux,
> > > > it means that the file will receive the security context of its task.
> > > >
> > > > The ability to limit fexecve on memfd has been of interest to avoid
> > > > potential pitfalls where /proc/self/exe or similar would be executed
> > > > [1][2]. Reuse the "execute_no_trans" and "entrypoint" access vectors,
> > > > similarly to the file class. These access vectors may not make sense for
> > > > the existing "anon_inode" class. Therefore, define and assign a new
> > > > class "memfd_file" to support such access vectors.
> > > >
> > > > Guard these changes behind a new policy capability named "memfd_class".
> > > >
> > > > [1] https://crbug.com/1305267
> > > > [2] https://lore.kernel.org/lkml/20221215001205.51969-1-jeffxu@google.com/
> > > >
> > > > Signed-off-by: Thiébaud Weksteen <tweek@google.com>
> > > > Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> > > > Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com>
>
> ...
>
> > > Hugh, Baolin, and shmem/mm folks, are you okay with the changes above? If
> > > so it would be nice to get an ACK from one of you.
> >
> > So far as I can tell, seems okay to me:
> > Acked-by: Hugh Dickins <hughd@google.com>
> >
> > If I'd responded earlier (sorry), I would have asked for it just to use
> > &QSTR("[memfd]") directly in the call, rather than indirecting through
> > unnecessary #define MEMFD_ANON_NAME "[memfd]"; never mind, that's all.
> >
Thanks for the review Hugh. In our case, it is necessary to expose
MEMFD_ANON_NAME as there is a string comparison done in
security/selinux/hooks.c (see selinux_inode_init_security_anon
changes).
I would argue it is cleaner to reference the same constant. The
alternative here would be to have 2 copies of it, with the risk of
them being out-of-sync at some point.
> > Please do take this, along with the rest, through your security tree:
> > mm.git contains no conflicting change to mm/memfd.c at present.
>
> Thanks Hugh, it turns out we ended up having a discussion on the
> SELinux side (proper return values for error conditions) and I'm going
> to hold off on this until after the upcoming merge window to give time
> for that discussion to run its course. The good news is that gives
> Thiébaud an opportunity to do the qstr fixup you wanted.
>
> Thiébaud, are you okay with making the change Hugh has requested?
>
> --
> paul-moore.com
^ permalink raw reply
* Re: [PATCH] memfd,selinux: call security_inode_init_security_anon
From: Hugh Dickins @ 2025-09-17 1:08 UTC (permalink / raw)
To: Thiébaud Weksteen
Cc: Paul Moore, Hugh Dickins, James Morris, Stephen Smalley,
Jeff Vander Stoep, Nick Kralevich, Jeff Xu, Baolin Wang,
linux-kernel, linux-security-module, selinux, linux-mm
In-Reply-To: <CA+zpnLe8Z=2qY3rVCyZXyGPOaO2a0V2BVrdMkuwKs789igGCzw@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1517 bytes --]
On Wed, 17 Sep 2025, Thiébaud Weksteen wrote:
> On Wed, Sep 17, 2025 at 1:26 AM Paul Moore <paul@paul-moore.com> wrote:
> > >
> > > So far as I can tell, seems okay to me:
> > > Acked-by: Hugh Dickins <hughd@google.com>
> > >
> > > If I'd responded earlier (sorry), I would have asked for it just to use
> > > &QSTR("[memfd]") directly in the call, rather than indirecting through
> > > unnecessary #define MEMFD_ANON_NAME "[memfd]"; never mind, that's all.
> > >
>
> Thanks for the review Hugh. In our case, it is necessary to expose
> MEMFD_ANON_NAME as there is a string comparison done in
> security/selinux/hooks.c (see selinux_inode_init_security_anon
> changes).
> I would argue it is cleaner to reference the same constant. The
> alternative here would be to have 2 copies of it, with the risk of
> them being out-of-sync at some point.
Oh, I'm sorry, I simply misread your patch, and thought that the
#define MEMFD_ANON_NAME "[memfd]" came at the start of mm/memfd.c,
whereas it's in include/linux/memfd.h just before mm/memfd.c.
Yes, you're perfectly correct to do it that way then, ignore me.
(I do have to reflect on why three instances of MEMFD_ANON_NAME
are safer than two instances of "[memfd]", but you are correct.)
That does lead me to look quickly at the security/selinux/hooks.c
end of the patch: I don't particularly love what I see there, but
that's none of my business, you and Paul have constraints to meet
there which I'm entirely unfamiliar with.
Hugh
^ permalink raw reply
* Re: [PATCH v2 0/7] fs/9p: Reuse inode based on path (in addition to qid)
From: Christian Schoenebeck @ 2025-09-17 9:52 UTC (permalink / raw)
To: Dominique Martinet, Tingmao Wang
Cc: Mickaël Salaün, Eric Van Hensbergen, Latchesar Ionkov,
v9fs, Günther Noack, linux-security-module, Jan Kara,
Amir Goldstein, Matthew Bobrowski, Al Viro, Christian Brauner,
linux-fsdevel
In-Reply-To: <f2c94b0a-2f1e-425a-bda1-f2d141acdede@maowtm.org>
On Wednesday, September 17, 2025 1:59:21 AM CEST Tingmao Wang wrote:
> On 9/16/25 20:22, Christian Schoenebeck wrote:
> > On Tuesday, September 16, 2025 4:01:40 PM CEST Tingmao Wang wrote:
[...]
> > I see that you are proposing an option for your proposed qid based
> > re-using of dentries. I don't think it should be on by default though,
> > considering what we already discussed (e.g. inodes recycled by ext4, but
> > also not all 9p servers handling inode collisions).
>
> Just to be clear, this approach (Landlock holding a fid reference, then
> using the qid as a key to search for rules when a Landlocked process
> accesses the previously remembered file, possibly after the file has been
> moved on the server) would only be in Landlock, and would only affect
> Landlock, not 9pfs (so not sure what you meant by "re-using of dentries").
>
> The idea behind holding a fid reference within Landlock is that, because
> we have the file open, the inode would not get recycled in ext4, and thus
> no other file will reuse the qid, until we close that reference (when the
> Landlock domain terminates, or when the 9p filesystem is unmounted)
So far I only had a glimpse on your kernel patches and had the impression that
they are changing behaviour for all users, since you are touching dentry
lookup.
> > For all open FIDs QEMU retains a descriptor to the file/directory.
> >
> > Which 9p message do you see sent to server, Trename or Trenameat?
> >
> > Does this always happen to you or just sometimes, i.e. under heavy load?
>
> Always happen, see log: (no Trename since the rename is done on the host)
[...]
> Somehow if I rename in the guest, it all works, even though it's using the
> same fid 2 (and it didn't ask QEMU to walk the new path)
Got it. Even though QEMU *should* hold a file descriptor (or a DIR* stream,
which should imply a file descriptor), there is still a path string stored at
V9fsFidState and that path being processed at some places, probably because
there are path based and FID based variants (e.g Trename vs. Trenameat). Maybe
that clashes somewhere, not sure. So I fear you would need to debug this.
/Christian
^ permalink raw reply
* Re: [PATCH v2 0/7] fs/9p: Reuse inode based on path (in addition to qid)
From: Mickaël Salaün @ 2025-09-17 15:00 UTC (permalink / raw)
To: Christian Schoenebeck
Cc: Dominique Martinet, Tingmao Wang, Eric Van Hensbergen,
Latchesar Ionkov, v9fs, Günther Noack, linux-security-module,
Jan Kara, Amir Goldstein, Matthew Bobrowski, Al Viro,
Christian Brauner, linux-fsdevel
In-Reply-To: <3774641.iishnSSGpB@silver>
On Wed, Sep 17, 2025 at 11:52:35AM +0200, Christian Schoenebeck wrote:
> On Wednesday, September 17, 2025 1:59:21 AM CEST Tingmao Wang wrote:
> > On 9/16/25 20:22, Christian Schoenebeck wrote:
> > > On Tuesday, September 16, 2025 4:01:40 PM CEST Tingmao Wang wrote:
> [...]
> > > I see that you are proposing an option for your proposed qid based
> > > re-using of dentries. I don't think it should be on by default though,
> > > considering what we already discussed (e.g. inodes recycled by ext4, but
> > > also not all 9p servers handling inode collisions).
> >
> > Just to be clear, this approach (Landlock holding a fid reference, then
> > using the qid as a key to search for rules when a Landlocked process
> > accesses the previously remembered file, possibly after the file has been
> > moved on the server) would only be in Landlock, and would only affect
> > Landlock, not 9pfs (so not sure what you meant by "re-using of dentries").
> >
> > The idea behind holding a fid reference within Landlock is that, because
> > we have the file open, the inode would not get recycled in ext4, and thus
> > no other file will reuse the qid, until we close that reference (when the
> > Landlock domain terminates, or when the 9p filesystem is unmounted)
>
> So far I only had a glimpse on your kernel patches and had the impression that
> they are changing behaviour for all users, since you are touching dentry
> lookup.
I think we should not hold dentries because:
- they reference other dentries (i.e. a file hierarchy),
- they block umount and I'm convinced the VFS (and users) are not going
to like long-lived dentries,
- Landlock and inotify don't need dentries, just inodes.
I'm wondering why fid are referenced by dentries instead of inodes.
The need for Landlock is to be able to match an inode with a previously
seen one. Not all LSM hooks (nor VFS internals) always have access to
dentries, but they do have access to inodes.
>
> > > For all open FIDs QEMU retains a descriptor to the file/directory.
> > >
> > > Which 9p message do you see sent to server, Trename or Trenameat?
> > >
> > > Does this always happen to you or just sometimes, i.e. under heavy load?
> >
> > Always happen, see log: (no Trename since the rename is done on the host)
> [...]
> > Somehow if I rename in the guest, it all works, even though it's using the
> > same fid 2 (and it didn't ask QEMU to walk the new path)
>
> Got it. Even though QEMU *should* hold a file descriptor (or a DIR* stream,
It's reasonable to assume that QEMU and other should hold opened fid In
practice, this might not always be the case, but let's move on and
consider that a 9p server bug.
Landlock and fanotify need some guarantees on opened files, and we
cannot consider every server bug. For Landlock, inode may get an
"ephemeral tag" (with the Landlock object mechanism) to match previously
seen inodes. In a perfect world, Landlock could keep a reference on 9p
inodes (as for other filesystems) and these inodes would always match
the same file. In practice this is not the case, but the 9p client
requirements and the Landlock requirements are not exactly the same.
A 9p client (the kernel) wants to safely deal with duplicated qid, which
should not happen but still happen in practice as explained before.
On the other side, Landlock wants to not deny access to allowed files
(currently identified by their inodes), but I think it would be
reasonable to allow access theoretically denied (i.e. not allowed to be
precise, because of the denied by default mechanism) files because of a
9p server bug mishandling qid (e.g. mapping them to recycled ext4
inodes).
All that to say that it looks reasonable for Landlock to trust the
filesystem, and by that I mean all its dependencies, including the 9p
server, to not have bugs.
Another advantage to rely on qid and server-side opened files is that we
get (in theory) the same semantic as when Landlock is used with local
filesystems (e.g. files moved on the server should still be correctly
identified by Landlock on the client).
> which should imply a file descriptor), there is still a path string stored at
> V9fsFidState and that path being processed at some places, probably because
> there are path based and FID based variants (e.g Trename vs. Trenameat). Maybe
> that clashes somewhere, not sure. So I fear you would need to debug this.
Good to know that it is not a legitimate behavior for a 9p client.
^ permalink raw reply
* Re: [PATCH v2 0/7] fs/9p: Reuse inode based on path (in addition to qid)
From: Mickaël Salaün @ 2025-09-17 15:00 UTC (permalink / raw)
To: Christian Schoenebeck
Cc: Tingmao Wang, Dominique Martinet, Eric Van Hensbergen,
Latchesar Ionkov, v9fs, Günther Noack, linux-security-module,
Jan Kara, Amir Goldstein, Matthew Bobrowski, Al Viro,
Christian Brauner, linux-fsdevel
In-Reply-To: <14530343.U1M6xoFM3Z@silver>
On Mon, Sep 15, 2025 at 04:10:07PM +0200, Christian Schoenebeck wrote:
> On Monday, September 15, 2025 2:53:14 PM CEST Dominique Martinet wrote:
> [...]
> > > 1. The qid is 9pfs internal data, and we may need extra API for 9pfs to
> > >
> > > expose this to Landlock. On 64bit, this is easy as it's just the inode
> > > number (offset by 2), which we can already get from the struct inode.
> > > But perhaps on 32bit we need a way to expose the full 64bit server-sent
> > > qid to Landlock (or other kernel subsystems), if we're going to do
> > > this.
> >
> > I'm not sure how much effort we want to spend on 32bit: as far as I
> > know, if we have inode number collision on 32 bit we're already in
> > trouble (tools like tar will consider such files to be hardlink of each
> > other and happily skip reading data, producing corrupted archives);
> > this is not a happy state but I don't know how to do better in any
> > reasonable way, so we can probably keep a similar limitation for 32bit
> > and use inode number directly...
>
> I agree, on 32-bit the game is lost.
>
> One way that would come to my mind though: exposing the full qid path as xattr
> on 32-bit, e.g. via "system.9pfs_qid" or something like that.
Another way to always deal with 64-bit values, even on 32-bit
architectures, would be to implement inode->i_op->getattr(), but that
could have side effects for 9p users expecting the current behavior.
>
> > > 2. Even though qids are supposed to be unique across the lifetime of a
> > >
> > > filesystem (including deleted files), this is not the case even for
> > > QEMU in multidevs=remap mode, when running on ext4, as tested on QEMU
> > > 10.1.0.
> >
> > I'm not familiar with the qid remap implementation in qemu, but I'm
> > curious in what case you hit that.
> > Deleting and recreating files? Or as you seem to say below the 'qid' is
> > "freed" when fd is closed qemu-side and re-used by later open of other
> > files?
>
> The inode remap algorithm in QEMU's 9p server was designed to prevent inode
> number collisions of equally numbered inodes of *different* *devices* on host,
> exposed to guest via the same 9p mount (which appears as only one 9pfs device
> on guest). Basis for this however is still the underlying filesystem's inode
> number on host.
>
> So yes, ext4 re-uses inode numbers of deleted files, and when that happens, a
> new file appears with the same qid path as the previously deleted file with
> QEMU.
>
> /Christian
>
>
>
^ permalink raw reply
* Re: [pcmoore-lsm:working-lsm_init_rework 40/41] security/lsm_init.c:516:undefined reference to `securityfs_init'
From: Paul Moore @ 2025-09-17 18:02 UTC (permalink / raw)
To: kernel test robot
Cc: oe-kbuild-all, Casey Schaufler, John Johansen,
linux-security-module
In-Reply-To: <202509171951.rXOi8Oez-lkp@intel.com>
On Wed, Sep 17, 2025 at 7:57 AM kernel test robot <lkp@intel.com> wrote:
>
> tree: https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git working-lsm_init_rework
> head: 8ea7358ca612bb85eef9179ae746edeb5f1dc9ac
> commit: bf19c9b17d8d2f27e2031822337e8d137b2c6224 [40/41] lsm: consolidate all of the LSM framework initcalls
> config: x86_64-randconfig-003-20250917 (https://download.01.org/0day-ci/archive/20250917/202509171951.rXOi8Oez-lkp@intel.com/config)
> compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
> reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250917/202509171951.rXOi8Oez-lkp@intel.com/reproduce)
>
> If you fix the issue in a separate patch/commit (i.e. not just a new version of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot <lkp@intel.com>
> | Closes: https://lore.kernel.org/oe-kbuild-all/202509171951.rXOi8Oez-lkp@intel.com/
>
> All errors (new ones prefixed by >>):
>
> ld: security/lsm_init.o: in function `security_initcall_core':
> >> security/lsm_init.c:516:(.init.text+0x101b): undefined reference to `securityfs_init'
>
>
> vim +516 security/lsm_init.c
>
> 508
> 509 /**
> 510 * security_initcall_core - Run the LSM core initcalls
> 511 */
> 512 static int __init security_initcall_core(void)
> 513 {
> 514 int rc_sfs, rc_lsm;
> 515
> > 516 rc_sfs = securityfs_init();
> 517 rc_lsm = lsm_initcall(core);
> 518
> 519 return (rc_sfs ? rc_sfs : rc_lsm);
> 520 }
> 521 core_initcall(security_initcall_core);
> 522
>
> --
> 0-DAY CI Kernel Test Service
> https://github.com/intel/lkp-tests/wiki
Fixed in the lsm/working-lsm_init_rework branch in case anyone wants
to take a look. However, as the fix really doesn't have anything to
do with the IMA/EVM patch, I'll wait to repost the patchset until Mimi
has a chance to look at the IMA/EVM patch from Roberto.
--
paul-moore.com
^ permalink raw reply
* Re: [pcmoore-lsm:working-lsm_init_rework 9/41] security/lsm_init.c:400:25: sparse: sparse: cast removes address space '__rcu' of expression
From: Paul Moore @ 2025-09-17 18:02 UTC (permalink / raw)
To: kernel test robot
Cc: oe-kbuild-all, Kees Cook, John Johansen, Casey Schaufler,
linux-security-module
In-Reply-To: <202509172014.2AWygxKf-lkp@intel.com>
On Wed, Sep 17, 2025 at 9:00 AM kernel test robot <lkp@intel.com> wrote:
>
> tree: https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git working-lsm_init_rework
> head: 8ea7358ca612bb85eef9179ae746edeb5f1dc9ac
> commit: 9cb4dac58e465101fea4aa4f9169f1ea04149da8 [9/41] lsm: split the init code out into lsm_init.c
> config: nios2-randconfig-r133-20250917 (https://download.01.org/0day-ci/archive/20250917/202509172014.2AWygxKf-lkp@intel.com/config)
> compiler: nios2-linux-gcc (GCC) 9.5.0
> reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250917/202509172014.2AWygxKf-lkp@intel.com/reproduce)
>
> If you fix the issue in a separate patch/commit (i.e. not just a new version of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot <lkp@intel.com>
> | Closes: https://lore.kernel.org/oe-kbuild-all/202509172014.2AWygxKf-lkp@intel.com/
>
> sparse warnings: (new ones prefixed by >>)
> >> security/lsm_init.c:400:25: sparse: sparse: cast removes address space '__rcu' of expression
>
> vim +/__rcu +400 security/lsm_init.c
>
> 348
> 349 static void __init ordered_lsm_init(void)
> 350 {
> 351 struct lsm_info **lsm;
> 352
> 353 if (chosen_lsm_order) {
> 354 if (chosen_major_lsm) {
> 355 pr_warn("security=%s is ignored because it is superseded by lsm=%s\n",
> 356 chosen_major_lsm, chosen_lsm_order);
> 357 chosen_major_lsm = NULL;
> 358 }
> 359 ordered_lsm_parse(chosen_lsm_order, "cmdline");
> 360 } else
> 361 ordered_lsm_parse(builtin_lsm_order, "builtin");
> 362
> 363 for (lsm = ordered_lsms; *lsm; lsm++)
> 364 prepare_lsm(*lsm);
> 365
> 366 report_lsm_order();
> 367
> 368 init_debug("cred blob size = %d\n", blob_sizes.lbs_cred);
> 369 init_debug("file blob size = %d\n", blob_sizes.lbs_file);
> 370 init_debug("ib blob size = %d\n", blob_sizes.lbs_ib);
> 371 init_debug("inode blob size = %d\n", blob_sizes.lbs_inode);
> 372 init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc);
> 373 #ifdef CONFIG_KEYS
> 374 init_debug("key blob size = %d\n", blob_sizes.lbs_key);
> 375 #endif /* CONFIG_KEYS */
> 376 init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg);
> 377 init_debug("sock blob size = %d\n", blob_sizes.lbs_sock);
> 378 init_debug("superblock blob size = %d\n", blob_sizes.lbs_superblock);
> 379 init_debug("perf event blob size = %d\n", blob_sizes.lbs_perf_event);
> 380 init_debug("task blob size = %d\n", blob_sizes.lbs_task);
> 381 init_debug("tun device blob size = %d\n", blob_sizes.lbs_tun_dev);
> 382 init_debug("xattr slots = %d\n", blob_sizes.lbs_xattr_count);
> 383 init_debug("bdev blob size = %d\n", blob_sizes.lbs_bdev);
> 384 init_debug("bpf map blob size = %d\n", blob_sizes.lbs_bpf_map);
> 385 init_debug("bpf prog blob size = %d\n", blob_sizes.lbs_bpf_prog);
> 386 init_debug("bpf token blob size = %d\n", blob_sizes.lbs_bpf_token);
> 387
> 388 /*
> 389 * Create any kmem_caches needed for blobs
> 390 */
> 391 if (blob_sizes.lbs_file)
> 392 lsm_file_cache = kmem_cache_create("lsm_file_cache",
> 393 blob_sizes.lbs_file, 0,
> 394 SLAB_PANIC, NULL);
> 395 if (blob_sizes.lbs_inode)
> 396 lsm_inode_cache = kmem_cache_create("lsm_inode_cache",
> 397 blob_sizes.lbs_inode, 0,
> 398 SLAB_PANIC, NULL);
> 399
> > 400 lsm_early_cred((struct cred *) current->cred);
> 401 lsm_early_task(current);
> 402 for (lsm = ordered_lsms; *lsm; lsm++)
> 403 initialize_lsm(*lsm);
> 404 }
> 405
>
> --
> 0-DAY CI Kernel Test Service
> https://github.com/intel/lkp-tests/wiki
Fixed in the lsm/working-lsm_init_rework branch in case anyone wants
to take a look. However, as the fix really doesn't have anything to
do with the IMA/EVM patch, I'll wait to repost the patchset until Mimi
has a chance to look at the IMA/EVM patch from Roberto.
--
paul-moore.com
^ permalink raw reply
* [PATCH v3] memfd,selinux: call security_inode_init_security_anon
From: Thiébaud Weksteen @ 2025-09-18 2:04 UTC (permalink / raw)
To: Paul Moore, James Morris, Stephen Smalley, Hugh Dickins,
Jeff Vander Stoep, Nick Kralevich, Jeff Xu, Baolin Wang,
Isaac Manjarres
Cc: Thiébaud Weksteen, linux-kernel, linux-security-module,
selinux, linux-mm
Prior to this change, no security hooks were called at the creation of a
memfd file. It means that, for SELinux as an example, it will receive
the default type of the filesystem that backs the in-memory inode. In
most cases, that would be tmpfs, but if MFD_HUGETLB is passed, it will
be hugetlbfs. Both can be considered implementation details of memfd.
It also means that it is not possible to differentiate between a file
coming from memfd_create and a file coming from a standard tmpfs mount
point.
Additionally, no permission is validated at creation, which differs from
the similar memfd_secret syscall.
Call security_inode_init_security_anon during creation. This ensures
that the file is setup similarly to other anonymous inodes. On SELinux,
it means that the file will receive the security context of its task.
The ability to limit fexecve on memfd has been of interest to avoid
potential pitfalls where /proc/self/exe or similar would be executed
[1][2]. Reuse the "execute_no_trans" and "entrypoint" access vectors,
similarly to the file class. These access vectors may not make sense for
the existing "anon_inode" class. Therefore, define and assign a new
class "memfd_file" to support such access vectors.
Guard these changes behind a new policy capability named "memfd_class".
[1] https://crbug.com/1305267
[2] https://lore.kernel.org/lkml/20221215001205.51969-1-jeffxu@google.com/
Signed-off-by: Thiébaud Weksteen <tweek@google.com>
---
Changes since v2:
- Add WARN_ON when using unexpected class. Return -EACCES instead
of -EPERM
- Remove extra new line
- Rebase on selinux/dev
Changes since v1:
- Move test of class earlier in selinux_bprm_creds_for_exec
- Remove duplicate call to security_transition_sid
Changes since RFC:
- Remove enum argument, simply compare the anon inode name
- Introduce a policy capability for compatility
- Add validation of class in selinux_bprm_creds_for_exec
include/linux/memfd.h | 2 ++
mm/memfd.c | 14 ++++++++++--
security/selinux/hooks.c | 26 +++++++++++++++++-----
security/selinux/include/classmap.h | 2 ++
security/selinux/include/policycap.h | 1 +
security/selinux/include/policycap_names.h | 1 +
security/selinux/include/security.h | 5 +++++
7 files changed, 44 insertions(+), 7 deletions(-)
diff --git a/include/linux/memfd.h b/include/linux/memfd.h
index 6f606d9573c3..cc74de3dbcfe 100644
--- a/include/linux/memfd.h
+++ b/include/linux/memfd.h
@@ -4,6 +4,8 @@
#include <linux/file.h>
+#define MEMFD_ANON_NAME "[memfd]"
+
#ifdef CONFIG_MEMFD_CREATE
extern long memfd_fcntl(struct file *file, unsigned int cmd, unsigned int arg);
struct folio *memfd_alloc_folio(struct file *memfd, pgoff_t idx);
diff --git a/mm/memfd.c b/mm/memfd.c
index bbe679895ef6..63b439eb402a 100644
--- a/mm/memfd.c
+++ b/mm/memfd.c
@@ -433,6 +433,8 @@ static struct file *alloc_file(const char *name, unsigned int flags)
{
unsigned int *file_seals;
struct file *file;
+ struct inode *inode;
+ int err = 0;
if (flags & MFD_HUGETLB) {
file = hugetlb_file_setup(name, 0, VM_NORESERVE,
@@ -444,12 +446,20 @@ static struct file *alloc_file(const char *name, unsigned int flags)
}
if (IS_ERR(file))
return file;
+
+ inode = file_inode(file);
+ err = security_inode_init_security_anon(inode,
+ &QSTR(MEMFD_ANON_NAME), NULL);
+ if (err) {
+ fput(file);
+ file = ERR_PTR(err);
+ return file;
+ }
+
file->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
file->f_flags |= O_LARGEFILE;
if (flags & MFD_NOEXEC_SEAL) {
- struct inode *inode = file_inode(file);
-
inode->i_mode &= ~0111;
file_seals = memfd_file_seals_ptr(file);
if (file_seals) {
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 0e47b4bb8d40..2b685f9dd61d 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -93,6 +93,7 @@
#include <linux/fanotify.h>
#include <linux/io_uring/cmd.h>
#include <uapi/linux/lsm.h>
+#include <linux/memfd.h>
#include "avc.h"
#include "objsec.h"
@@ -2319,6 +2320,10 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
new_tsec = selinux_cred(bprm->cred);
isec = inode_security(inode);
+ if (WARN_ON(isec->sclass != SECCLASS_FILE &&
+ isec->sclass != SECCLASS_MEMFD_FILE))
+ return -EACCES;
+
/* Default to the current task SID. */
new_tsec->sid = old_tsec->sid;
new_tsec->osid = old_tsec->sid;
@@ -2371,8 +2376,8 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
ad.u.file = bprm->file;
if (new_tsec->sid == old_tsec->sid) {
- rc = avc_has_perm(old_tsec->sid, isec->sid,
- SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad);
+ rc = avc_has_perm(old_tsec->sid, isec->sid, isec->sclass,
+ FILE__EXECUTE_NO_TRANS, &ad);
if (rc)
return rc;
} else {
@@ -2382,8 +2387,8 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
if (rc)
return rc;
- rc = avc_has_perm(new_tsec->sid, isec->sid,
- SECCLASS_FILE, FILE__ENTRYPOINT, &ad);
+ rc = avc_has_perm(new_tsec->sid, isec->sid, isec->sclass,
+ FILE__ENTRYPOINT, &ad);
if (rc)
return rc;
@@ -2978,10 +2983,18 @@ static int selinux_inode_init_security_anon(struct inode *inode,
struct common_audit_data ad;
struct inode_security_struct *isec;
int rc;
+ bool is_memfd = false;
if (unlikely(!selinux_initialized()))
return 0;
+ if (name != NULL && name->name != NULL &&
+ !strcmp(name->name, MEMFD_ANON_NAME)) {
+ if (!selinux_policycap_memfd_class())
+ return 0;
+ is_memfd = true;
+ }
+
isec = selinux_inode(inode);
/*
@@ -3001,7 +3014,10 @@ static int selinux_inode_init_security_anon(struct inode *inode,
isec->sclass = context_isec->sclass;
isec->sid = context_isec->sid;
} else {
- isec->sclass = SECCLASS_ANON_INODE;
+ if (is_memfd)
+ isec->sclass = SECCLASS_MEMFD_FILE;
+ else
+ isec->sclass = SECCLASS_ANON_INODE;
rc = security_transition_sid(
sid, sid,
isec->sclass, name, &isec->sid);
diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h
index 5665aa5e7853..3ec85142771f 100644
--- a/security/selinux/include/classmap.h
+++ b/security/selinux/include/classmap.h
@@ -179,6 +179,8 @@ const struct security_class_mapping secclass_map[] = {
{ "anon_inode", { COMMON_FILE_PERMS, NULL } },
{ "io_uring", { "override_creds", "sqpoll", "cmd", "allowed", NULL } },
{ "user_namespace", { "create", NULL } },
+ { "memfd_file",
+ { COMMON_FILE_PERMS, "execute_no_trans", "entrypoint", NULL } },
/* last one */ { NULL, {} }
};
diff --git a/security/selinux/include/policycap.h b/security/selinux/include/policycap.h
index 135a969f873c..231d02227e59 100644
--- a/security/selinux/include/policycap.h
+++ b/security/selinux/include/policycap.h
@@ -18,6 +18,7 @@ enum {
POLICYDB_CAP_NETIF_WILDCARD,
POLICYDB_CAP_GENFS_SECLABEL_WILDCARD,
POLICYDB_CAP_FUNCTIONFS_SECLABEL,
+ POLICYDB_CAP_MEMFD_CLASS,
__POLICYDB_CAP_MAX
};
#define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1)
diff --git a/security/selinux/include/policycap_names.h b/security/selinux/include/policycap_names.h
index ff8882887651..454dab37bda3 100644
--- a/security/selinux/include/policycap_names.h
+++ b/security/selinux/include/policycap_names.h
@@ -21,6 +21,7 @@ const char *const selinux_policycap_names[__POLICYDB_CAP_MAX] = {
"netif_wildcard",
"genfs_seclabel_wildcard",
"functionfs_seclabel",
+ "memfd_class",
};
/* clang-format on */
diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
index 0f954a40d3fc..5d1dad8058b1 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -209,6 +209,11 @@ static inline bool selinux_policycap_functionfs_seclabel(void)
selinux_state.policycap[POLICYDB_CAP_FUNCTIONFS_SECLABEL]);
}
+static inline bool selinux_policycap_memfd_class(void)
+{
+ return READ_ONCE(selinux_state.policycap[POLICYDB_CAP_MEMFD_CLASS]);
+}
+
struct selinux_policy_convert_data;
struct selinux_load_state {
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply related
* Re: [PATCH v4 24/34] loadpin: move initcalls to the LSM framework
From: Mimi Zohar @ 2025-09-18 11:15 UTC (permalink / raw)
To: Paul Moore, linux-security-module, linux-integrity, selinux
Cc: John Johansen, Roberto Sassu, Fan Wu, Mickaël Salaün,
Günther Noack, Kees Cook, Micah Morton, Casey Schaufler,
Tetsuo Handa, Nicolas Bouchinet, Xiu Jianfeng
In-Reply-To: <20250916220355.252592-60-paul@paul-moore.com>
On Tue, 2025-09-16 at 18:03 -0400, Paul Moore wrote:
> Acked-by: Kees Cook <kees@kernel.org>
> Reviewed-by: John Johansen <john.johhansen@canonical.com>
> Signed-off-by: Paul Moore <paul@paul-moore.com>
For the first couple of iterations, the patch descriptions needed to be added or
improved upon. Some of the patch descriptions are still missing (e.g. 25, 27,
etc). Is this intentional because you feel it is redundant or simply an
oversight because checkpatch.pl is not flagging it? Checkpatch normally flags
missing patch descriptions, but isn't flagging it now because of the additional
tags.
FYI, teaching newbies how to break up a patch set is not easy. This patch set
is nicely broken up and would be a good example. However, leaving out the patch
description would be teaching the wrong thing.
Mimi
> ---
> security/loadpin/loadpin.c | 15 ++++++++-------
> 1 file changed, 8 insertions(+), 7 deletions(-)
>
> diff --git a/security/loadpin/loadpin.c b/security/loadpin/loadpin.c
> index b9ddf05c5c16..273ffbd6defe 100644
> --- a/security/loadpin/loadpin.c
> +++ b/security/loadpin/loadpin.c
> @@ -270,11 +270,6 @@ static int __init loadpin_init(void)
> return 0;
> }
>
> -DEFINE_LSM(loadpin) = {
> - .id = &loadpin_lsmid,
> - .init = loadpin_init,
> -};
> -
> #ifdef CONFIG_SECURITY_LOADPIN_VERITY
>
> enum loadpin_securityfs_interface_index {
> @@ -434,10 +429,16 @@ static int __init init_loadpin_securityfs(void)
> return 0;
> }
>
> -fs_initcall(init_loadpin_securityfs);
> -
> #endif /* CONFIG_SECURITY_LOADPIN_VERITY */
>
> +DEFINE_LSM(loadpin) = {
> + .id = &loadpin_lsmid,
> + .init = loadpin_init,
> +#ifdef CONFIG_SECURITY_LOADPIN_VERITY
> + .initcall_fs = init_loadpin_securityfs,
> +#endif /* CONFIG_SECURITY_LOADPIN_VERITY */
> +};
> +
> /* Should not be mutable after boot, so not listed in sysfs (perm == 0). */
> module_param(enforce, int, 0);
> MODULE_PARM_DESC(enforce, "Enforce module/firmware pinning");
^ permalink raw reply
* Re: [PATCH v4 15/34] lsm: rename/rework ordered_lsm_parse() to lsm_order_parse()
From: Mimi Zohar @ 2025-09-18 11:29 UTC (permalink / raw)
To: Paul Moore, linux-security-module, linux-integrity, selinux
Cc: John Johansen, Roberto Sassu, Fan Wu, Mickaël Salaün,
Günther Noack, Kees Cook, Micah Morton, Casey Schaufler,
Tetsuo Handa, Nicolas Bouchinet, Xiu Jianfeng
In-Reply-To: <20250916220355.252592-51-paul@paul-moore.com>
On Tue, 2025-09-16 at 18:03 -0400, Paul Moore wrote:
> Rename ordered_lsm_parse() to lsm_order_parse() for the sake of
> consistency with the other LSM initialization routines, and also
> do some minor rework of the function. Aside from some minor style
> decisions, the majority of the rework involved shuffling the order
> of the LSM_FLAG_LEGACY and LSM_ORDER_FIRST code so that the
> LSM_FLAG_LEGACY checks are handled first; it is important to note
> that this doesn't affect the order in which the LSMs are registered.
>
> Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
> Reviewed-by: John Johansen <john.johhansen@canonical.com>
> Signed-off-by: Paul Moore <paul@paul-moore.com>
> ---
> security/lsm_init.c | 82 ++++++++++++++++++++-------------------------
> 1 file changed, 37 insertions(+), 45 deletions(-)
>
> diff --git a/security/lsm_init.c b/security/lsm_init.c
> index a314484d7c2f..7b2491120fc8 100644
> --- a/security/lsm_init.c
> +++ b/security/lsm_init.c
> @@ -228,83 +228,75 @@ static void __init initialize_lsm(struct lsm_info *lsm)
> }
> }
>
> -/* Populate ordered LSMs list from comma-separated LSM name list. */
> -static void __init ordered_lsm_parse(const char *order, const char *origin)
> +/**
> + * lsm_order_parse - Parse the comma delimited LSM list
> + * @list: LSM list
> + * @src: source of the list
> + */
> +static void __init lsm_order_parse(const char *list, const char *src)
> {
> struct lsm_info *lsm;
> char *sep, *name, *next;
>
> - /* LSM_ORDER_FIRST is always first. */
> - lsm_for_each_raw(lsm) {
> - if (lsm->order == LSM_ORDER_FIRST)
> - lsm_order_append(lsm, " first");
> - }
> -
> - /* Process "security=", if given. */
> + /* Handle any Legacy LSM exclusions if one was specified. */
> if (lsm_order_legacy) {
> - struct lsm_info *major;
> -
> /*
> - * To match the original "security=" behavior, this
> - * explicitly does NOT fallback to another Legacy Major
> - * if the selected one was separately disabled: disable
> - * all non-matching Legacy Major LSMs.
> + * To match the original "security=" behavior, this explicitly
> + * does NOT fallback to another Legacy Major if the selected
> + * one was separately disabled: disable all non-matching
> + * Legacy Major LSMs.
> */
> - lsm_for_each_raw(major) {
> - if ((major->flags & LSM_FLAG_LEGACY_MAJOR) &&
> - strcmp(major->id->name, lsm_order_legacy) != 0) {
> - lsm_enabled_set(major, false);
> + lsm_for_each_raw(lsm) {
> + if ((lsm->flags & LSM_FLAG_LEGACY_MAJOR) &&
> + strcmp(lsm->id->name, lsm_order_legacy)) {
> + lsm_enabled_set(lsm, false);
> init_debug("security=%s disabled: %s (only one legacy major LSM)\n",
> - lsm_order_legacy, major->id->name);
> + lsm_order_legacy, lsm->id->name);
> }
> }
> }
>
> - sep = kstrdup(order, GFP_KERNEL);
> + /* LSM_ORDER_FIRST */
> + lsm_for_each_raw(lsm) {
> + if (lsm->order == LSM_ORDER_FIRST)
> + lsm_order_append(lsm, "first");
> + }
> +
> + /* Normal or "mutable" LSMs */
Paul, there's a reason for another set of eyes reviewing patches and yes, even,
comments. What are "mutable" LSMs?!
The above comment LSM_ORDER_FIRST is self describing. Here the word "order" is
missing from this comment.
-> Normal or "mutable" LSM ordering
Otherwise, Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
> + sep = kstrdup(list, GFP_KERNEL);
> next = sep;
> /* Walk the list, looking for matching LSMs. */
> while ((name = strsep(&next, ",")) != NULL) {
> - bool found = false;
> -
> lsm_for_each_raw(lsm) {
> - if (strcmp(lsm->id->name, name) == 0) {
> - if (lsm->order == LSM_ORDER_MUTABLE)
> - lsm_order_append(lsm, origin);
> - found = true;
> - }
> + if (!strcmp(lsm->id->name, name) &&
> + lsm->order == LSM_ORDER_MUTABLE)
> + lsm_order_append(lsm, src);
> }
> -
> - if (!found)
> - init_debug("%s ignored: %s (not built into kernel)\n",
> - origin, name);
> }
> + kfree(sep);
>
> - /* Process "security=", if given. */
> + /* Legacy LSM if specified. */
> if (lsm_order_legacy) {
> lsm_for_each_raw(lsm) {
> - if (lsm_order_exists(lsm))
> - continue;
> - if (strcmp(lsm->id->name, lsm_order_legacy) == 0)
> - lsm_order_append(lsm, "security=");
> + if (!strcmp(lsm->id->name, lsm_order_legacy))
> + lsm_order_append(lsm, src);
> }
> }
>
> - /* LSM_ORDER_LAST is always last. */
> + /* LSM_ORDER_LAST */
> lsm_for_each_raw(lsm) {
> if (lsm->order == LSM_ORDER_LAST)
> - lsm_order_append(lsm, " last");
> + lsm_order_append(lsm, "last");
> }
>
> - /* Disable all LSMs not in the ordered list. */
> + /* Disable all LSMs not previously enabled. */
> lsm_for_each_raw(lsm) {
> if (lsm_order_exists(lsm))
> continue;
> lsm_enabled_set(lsm, false);
> init_debug("%s skipped: %s (not in requested order)\n",
> - origin, lsm->id->name);
> + src, lsm->id->name);
> }
> -
> - kfree(sep);
> }
>
> /**
> @@ -322,9 +314,9 @@ static void __init lsm_init_ordered(void)
> lsm_order_legacy, lsm_order_cmdline);
> lsm_order_legacy = NULL;
> }
> - ordered_lsm_parse(lsm_order_cmdline, "cmdline");
> + lsm_order_parse(lsm_order_cmdline, "cmdline");
> } else
> - ordered_lsm_parse(lsm_order_builtin, "builtin");
> + lsm_order_parse(lsm_order_builtin, "builtin");
>
> lsm_order_for_each(lsm) {
> lsm_prepare(*lsm);
^ permalink raw reply
* Re: [PATCH v3] memfd,selinux: call security_inode_init_security_anon
From: Stephen Smalley @ 2025-09-18 13:21 UTC (permalink / raw)
To: Thiébaud Weksteen
Cc: Paul Moore, James Morris, Hugh Dickins, Jeff Vander Stoep,
Nick Kralevich, Jeff Xu, Baolin Wang, Isaac Manjarres,
linux-kernel, linux-security-module, selinux, linux-mm
In-Reply-To: <20250918020434.1612137-1-tweek@google.com>
On Wed, Sep 17, 2025 at 10:04 PM Thiébaud Weksteen <tweek@google.com> wrote:
>
> Prior to this change, no security hooks were called at the creation of a
> memfd file. It means that, for SELinux as an example, it will receive
> the default type of the filesystem that backs the in-memory inode. In
> most cases, that would be tmpfs, but if MFD_HUGETLB is passed, it will
> be hugetlbfs. Both can be considered implementation details of memfd.
>
> It also means that it is not possible to differentiate between a file
> coming from memfd_create and a file coming from a standard tmpfs mount
> point.
>
> Additionally, no permission is validated at creation, which differs from
> the similar memfd_secret syscall.
>
> Call security_inode_init_security_anon during creation. This ensures
> that the file is setup similarly to other anonymous inodes. On SELinux,
> it means that the file will receive the security context of its task.
>
> The ability to limit fexecve on memfd has been of interest to avoid
> potential pitfalls where /proc/self/exe or similar would be executed
> [1][2]. Reuse the "execute_no_trans" and "entrypoint" access vectors,
> similarly to the file class. These access vectors may not make sense for
> the existing "anon_inode" class. Therefore, define and assign a new
> class "memfd_file" to support such access vectors.
>
> Guard these changes behind a new policy capability named "memfd_class".
>
> [1] https://crbug.com/1305267
> [2] https://lore.kernel.org/lkml/20221215001205.51969-1-jeffxu@google.com/
>
> Signed-off-by: Thiébaud Weksteen <tweek@google.com>
Reviewed-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com>
For testing, I reverted the previous revert of the memfd_class policy
capability on selinux userspace, built and installed the modified
libsepol, and then followed the instructions in the testsuite commit
description.
Also confirmed manual execution of the tests in both enforcing and
permissive mode yields the expected behavior, and the WARN_ON() was
not triggered.
> ---
> Changes since v2:
> - Add WARN_ON when using unexpected class. Return -EACCES instead
> of -EPERM
> - Remove extra new line
> - Rebase on selinux/dev
>
> Changes since v1:
> - Move test of class earlier in selinux_bprm_creds_for_exec
> - Remove duplicate call to security_transition_sid
>
> Changes since RFC:
> - Remove enum argument, simply compare the anon inode name
> - Introduce a policy capability for compatility
> - Add validation of class in selinux_bprm_creds_for_exec
> include/linux/memfd.h | 2 ++
> mm/memfd.c | 14 ++++++++++--
> security/selinux/hooks.c | 26 +++++++++++++++++-----
> security/selinux/include/classmap.h | 2 ++
> security/selinux/include/policycap.h | 1 +
> security/selinux/include/policycap_names.h | 1 +
> security/selinux/include/security.h | 5 +++++
> 7 files changed, 44 insertions(+), 7 deletions(-)
>
> diff --git a/include/linux/memfd.h b/include/linux/memfd.h
> index 6f606d9573c3..cc74de3dbcfe 100644
> --- a/include/linux/memfd.h
> +++ b/include/linux/memfd.h
> @@ -4,6 +4,8 @@
>
> #include <linux/file.h>
>
> +#define MEMFD_ANON_NAME "[memfd]"
> +
> #ifdef CONFIG_MEMFD_CREATE
> extern long memfd_fcntl(struct file *file, unsigned int cmd, unsigned int arg);
> struct folio *memfd_alloc_folio(struct file *memfd, pgoff_t idx);
> diff --git a/mm/memfd.c b/mm/memfd.c
> index bbe679895ef6..63b439eb402a 100644
> --- a/mm/memfd.c
> +++ b/mm/memfd.c
> @@ -433,6 +433,8 @@ static struct file *alloc_file(const char *name, unsigned int flags)
> {
> unsigned int *file_seals;
> struct file *file;
> + struct inode *inode;
> + int err = 0;
>
> if (flags & MFD_HUGETLB) {
> file = hugetlb_file_setup(name, 0, VM_NORESERVE,
> @@ -444,12 +446,20 @@ static struct file *alloc_file(const char *name, unsigned int flags)
> }
> if (IS_ERR(file))
> return file;
> +
> + inode = file_inode(file);
> + err = security_inode_init_security_anon(inode,
> + &QSTR(MEMFD_ANON_NAME), NULL);
> + if (err) {
> + fput(file);
> + file = ERR_PTR(err);
> + return file;
> + }
> +
> file->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
> file->f_flags |= O_LARGEFILE;
>
> if (flags & MFD_NOEXEC_SEAL) {
> - struct inode *inode = file_inode(file);
> -
> inode->i_mode &= ~0111;
> file_seals = memfd_file_seals_ptr(file);
> if (file_seals) {
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 0e47b4bb8d40..2b685f9dd61d 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -93,6 +93,7 @@
> #include <linux/fanotify.h>
> #include <linux/io_uring/cmd.h>
> #include <uapi/linux/lsm.h>
> +#include <linux/memfd.h>
>
> #include "avc.h"
> #include "objsec.h"
> @@ -2319,6 +2320,10 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
> new_tsec = selinux_cred(bprm->cred);
> isec = inode_security(inode);
>
> + if (WARN_ON(isec->sclass != SECCLASS_FILE &&
> + isec->sclass != SECCLASS_MEMFD_FILE))
> + return -EACCES;
> +
> /* Default to the current task SID. */
> new_tsec->sid = old_tsec->sid;
> new_tsec->osid = old_tsec->sid;
> @@ -2371,8 +2376,8 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
> ad.u.file = bprm->file;
>
> if (new_tsec->sid == old_tsec->sid) {
> - rc = avc_has_perm(old_tsec->sid, isec->sid,
> - SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad);
> + rc = avc_has_perm(old_tsec->sid, isec->sid, isec->sclass,
> + FILE__EXECUTE_NO_TRANS, &ad);
> if (rc)
> return rc;
> } else {
> @@ -2382,8 +2387,8 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
> if (rc)
> return rc;
>
> - rc = avc_has_perm(new_tsec->sid, isec->sid,
> - SECCLASS_FILE, FILE__ENTRYPOINT, &ad);
> + rc = avc_has_perm(new_tsec->sid, isec->sid, isec->sclass,
> + FILE__ENTRYPOINT, &ad);
> if (rc)
> return rc;
>
> @@ -2978,10 +2983,18 @@ static int selinux_inode_init_security_anon(struct inode *inode,
> struct common_audit_data ad;
> struct inode_security_struct *isec;
> int rc;
> + bool is_memfd = false;
>
> if (unlikely(!selinux_initialized()))
> return 0;
>
> + if (name != NULL && name->name != NULL &&
> + !strcmp(name->name, MEMFD_ANON_NAME)) {
> + if (!selinux_policycap_memfd_class())
> + return 0;
> + is_memfd = true;
> + }
> +
> isec = selinux_inode(inode);
>
> /*
> @@ -3001,7 +3014,10 @@ static int selinux_inode_init_security_anon(struct inode *inode,
> isec->sclass = context_isec->sclass;
> isec->sid = context_isec->sid;
> } else {
> - isec->sclass = SECCLASS_ANON_INODE;
> + if (is_memfd)
> + isec->sclass = SECCLASS_MEMFD_FILE;
> + else
> + isec->sclass = SECCLASS_ANON_INODE;
> rc = security_transition_sid(
> sid, sid,
> isec->sclass, name, &isec->sid);
> diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h
> index 5665aa5e7853..3ec85142771f 100644
> --- a/security/selinux/include/classmap.h
> +++ b/security/selinux/include/classmap.h
> @@ -179,6 +179,8 @@ const struct security_class_mapping secclass_map[] = {
> { "anon_inode", { COMMON_FILE_PERMS, NULL } },
> { "io_uring", { "override_creds", "sqpoll", "cmd", "allowed", NULL } },
> { "user_namespace", { "create", NULL } },
> + { "memfd_file",
> + { COMMON_FILE_PERMS, "execute_no_trans", "entrypoint", NULL } },
> /* last one */ { NULL, {} }
> };
>
> diff --git a/security/selinux/include/policycap.h b/security/selinux/include/policycap.h
> index 135a969f873c..231d02227e59 100644
> --- a/security/selinux/include/policycap.h
> +++ b/security/selinux/include/policycap.h
> @@ -18,6 +18,7 @@ enum {
> POLICYDB_CAP_NETIF_WILDCARD,
> POLICYDB_CAP_GENFS_SECLABEL_WILDCARD,
> POLICYDB_CAP_FUNCTIONFS_SECLABEL,
> + POLICYDB_CAP_MEMFD_CLASS,
> __POLICYDB_CAP_MAX
> };
> #define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1)
> diff --git a/security/selinux/include/policycap_names.h b/security/selinux/include/policycap_names.h
> index ff8882887651..454dab37bda3 100644
> --- a/security/selinux/include/policycap_names.h
> +++ b/security/selinux/include/policycap_names.h
> @@ -21,6 +21,7 @@ const char *const selinux_policycap_names[__POLICYDB_CAP_MAX] = {
> "netif_wildcard",
> "genfs_seclabel_wildcard",
> "functionfs_seclabel",
> + "memfd_class",
> };
> /* clang-format on */
>
> diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
> index 0f954a40d3fc..5d1dad8058b1 100644
> --- a/security/selinux/include/security.h
> +++ b/security/selinux/include/security.h
> @@ -209,6 +209,11 @@ static inline bool selinux_policycap_functionfs_seclabel(void)
> selinux_state.policycap[POLICYDB_CAP_FUNCTIONFS_SECLABEL]);
> }
>
> +static inline bool selinux_policycap_memfd_class(void)
> +{
> + return READ_ONCE(selinux_state.policycap[POLICYDB_CAP_MEMFD_CLASS]);
> +}
> +
> struct selinux_policy_convert_data;
>
> struct selinux_load_state {
> --
> 2.51.0.384.g4c02a37b29-goog
>
^ permalink raw reply
* [PATCH v3 00/35] Compiler-Based Capability- and Locking-Analysis
From: Marco Elver @ 2025-09-18 13:59 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett,
Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn,
Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor,
Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa,
Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long,
kasan-dev, linux-crypto, linux-doc, linux-kbuild, linux-kernel,
linux-mm, linux-security-module, linux-sparse, llvm, rcu
Capability analysis is a C language extension, which enables statically
checking that user-definable "capabilities" are acquired and released where
required. An obvious application is lock-safety checking for the kernel's
various synchronization primitives (each of which represents a "capability"),
and checking that locking rules are not violated.
Clang originally called the feature "Thread Safety Analysis" [1], with
some terminology still using the thread-safety-analysis-only names. This
was later changed and the feature became more flexible, gaining the
ability to define custom "capabilities". Its foundations can be found in
"capability systems" [2], used to specify the permissibility of
operations to depend on some capability being held (or not held).
Because the feature is not just able to express capabilities related to
synchronization primitives, the naming chosen for the kernel departs
from Clang's initial "Thread Safety" nomenclature and refers to the
feature as "Capability Analysis" to avoid confusion. The implementation
still makes references to the older terminology in some places, such as
`-Wthread-safety` being the warning enabled option that also still
appears in diagnostic messages.
Enabling capability analysis can be seen as enabling a dialect of Linux
C with a Capability System.
Additional details can be found in the added kernel-doc documentation.
An LWN article covered v2 of the series: https://lwn.net/Articles/1012990/
[1] https://clang.llvm.org/docs/ThreadSafetyAnalysis.html
[2] https://www.cs.cornell.edu/talc/papers/capabilities.pdf
=== Development Approach ===
Prior art exists in the form of Sparse's context tracking. Locking
annotations on functions exist, so the concept of analyzing locking rules
is not foreign to the kernel's codebase.
However, Clang's analysis is more complete vs. Sparse's, with the
typical trade-offs in static analysis: improved completeness is
sacrificed for more possible false positives or additional annotations
required by the programmer. Numerous options exist to disable or opt out
certain code from analysis.
This series initially aimed to retain compatibility with Sparse, which
can provide tree-wide analysis of a subset of the capability analysis
introduced, but it was later decided to drop Sparse compatibility. For
the most part, the new (and old) keywords used for annotations remain
the same, and many of the pre-existing annotations remain valid.
One big question is how to enable this feature, given we end up with a
new dialect of C -- 2 approaches have been considered:
A. Tree-wide all-or-nothing approach. This approach requires tree-wide
changes, adding annotations or selective opt-outs. Making additional
primitives capability-enabled increases churn, esp. where maintainers
are unaware of the feature's existence and how to use it.
Because we can't change the programming language (even if from one C
dialect to another) of the kernel overnight, a different approach might
cause less friction.
B. A selective, incremental, and much less intrusive approach.
Maintainers of subsystems opt in their modules or directories into
"capability analysis" (via Makefile):
CAPABILITY_ANALYSIS_foo.o := y # foo.o only
CAPABILITY_ANALYSIS := y # all TUs
Most (eventually all) synchronization primitives and more
capabilities (including ones that could track "irq disabled",
"preemption" disabled, etc.) could be supported.
The approach taken by this series is B. This ensures that only
subsystems where maintainers are willing to deal with any warnings are
opted-in. Introducing the feature can be done incrementally, without
large tree-wide changes and adding numerous opt-outs and annotations to
the majority of code.
Note: Bart Van Assche concurrently worked on enabling -Wthread-safety:
https://lore.kernel.org/all/20250206175114.1974171-1-bvanassche@acm.org/
Bart's work has shown what it might take to go with approach A
(tree-wide, restricted to 'mutex' usage). This has shown that the
analysis finds real issues when applied to enough subsystems! We hope
this serves as motivation to eventually enable the analysis in as many
subsystems as possible, particularly subsystems that are not as easily
tested by CI systems and test robots.
=== Initial Uses ===
With this initial series, the following synchronization primitives are
supported: `raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`,
`seqlock_t`, `bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`,
`local_lock_t`, `ww_mutex`.
To demonstrate use of the feature on real kernel code, the series also
enables capability analysis for the following subsystems:
* kernel/kcov
* kernel/kcsan
* kernel/sched/
* lib/rhashtable
* lib/stackdepot
* mm/kfence
* security/tomoyo
* crypto/
The initial benefits are static detection of violations of locking
rules. As more capabilities are added, we would see more static checking
beyond what regular C can provide, all while remaining easy (read quick)
to use via the Clang compiler.
Note: The kernel already provides dynamic analysis tools Lockdep and
KCSAN for lock-safety checking and data-race detection respectively.
Unlike those, Clang's capability analysis is a compile-time static
analysis with no runtime impact. The static analysis complements
existing dynamic analysis tools, as it may catch some issues before
even getting into a running kernel, but is *not* a replacement for
whole-kernel testing with the dynamic analysis tools enabled!
=== Appendix ===
A Clang version that supports `-Wthread-safety-pointer` and the new
alias-analysis of capability pointers is required (from this version
onwards):
https://github.com/llvm/llvm-project/commit/b4c98fcbe1504841203e610c351a3227f36c92a4 [3]
This series is also available at this Git tree:
https://git.kernel.org/pub/scm/linux/kernel/git/melver/linux.git/log/?h=cap-analysis/dev
=== Changelog ===
v3:
- Bump min. Clang version to 22+ (unreleased), which now supports:
* re-entrancy via __attribute__((reentrant_capability));
* basic form of capability alias analysis [3] - which is the
biggest improvement since v2.
This was the result of conclusions from this discussion:
https://lore.kernel.org/all/CANpmjNPquO=W1JAh1FNQb8pMQjgeZAKCPQUAd7qUg=5pjJ6x=Q@mail.gmail.com/
- Rename __asserts_cap/__assert_cap to __assumes_cap/__assume_cap.
- Switch to DECLARE_LOCK_GUARD_1_ATTRS().
- Add __acquire_ret and __acquire_shared_ret helper macros - can be
used to define function-like macros that return objects which
contains a held capabilities. Works now because of capability alias
analysis.
- Add capability_unsafe_alias() helper, where the analysis rightfully
points out we're doing strange things with aliases but we don't
care.
- Support multi-argument attributes.
- Enable for kernel/sched/{core,fair}.c, kernel/kcsan.
- Drop drivers/tty changes (revisit later).
v2: https://lore.kernel.org/all/20250304092417.2873893-1-elver@google.com/
- Remove Sparse context tracking support - after the introduction of
Clang support, so that backports can skip removal of Sparse support.
- Remove __cond_lock() function-like helper.
- ww_mutex support.
- -Wthread-safety-addressof was reworked and committed in upstream
Clang as -Wthread-safety-pointer.
- Make __cond_acquires() and __cond_acquires_shared() take abstract
value, since compiler only cares about zero and non-zero.
- Rename __var_guarded_by to simply __guarded_by. Initially the idea
was to be explicit about if the variable itself or the pointed-to
data is guarded, but in the long-term, making this shorter might be
better.
- Likewise rename __ref_guarded_by to __pt_guarded_by.
- Introduce common header warning suppressions - this is a better
solution than guarding header inclusions with disable_ +
enable_capability_analysis(). Header suppressions are disabled when
selecting CONFIG_WARN_CAPABILITY_ANALYSIS_ALL=y. This bumps the
minimum Clang version required to 20+.
- Make the data_race() macro imply disabled capability analysis.
Writing capability_unsafe(data_race(..)) is unnecessarily verbose
and data_race() on its own already indicates something subtly unsafe
is happening. This change was made after analysis of a finding in
security/tomoyo.
- Enable analysis in the following subsystems as additional examples
of larger subsystem. Where it was obvious, the __guarded_by
attribute was added to lock-guarded variables to improve coverage.
* drivers/tty
* security/tomoyo
* crypto/
RFC v1: https://lore.kernel.org/lkml/20250206181711.1902989-1-elver@google.com
Marco Elver (35):
compiler_types: Move lock checking attributes to
compiler-capability-analysis.h
compiler-capability-analysis: Add infrastructure for Clang's
capability analysis
compiler-capability-analysis: Add test stub
Documentation: Add documentation for Compiler-Based Capability
Analysis
checkpatch: Warn about capability_unsafe() without comment
cleanup: Basic compatibility with capability analysis
lockdep: Annotate lockdep assertions for capability analysis
locking/rwlock, spinlock: Support Clang's capability analysis
compiler-capability-analysis: Change __cond_acquires to take return
value
locking/mutex: Support Clang's capability analysis
locking/seqlock: Support Clang's capability analysis
bit_spinlock: Include missing <asm/processor.h>
bit_spinlock: Support Clang's capability analysis
rcu: Support Clang's capability analysis
srcu: Support Clang's capability analysis
kref: Add capability-analysis annotations
locking/rwsem: Support Clang's capability analysis
locking/local_lock: Include missing headers
locking/local_lock: Support Clang's capability analysis
locking/ww_mutex: Support Clang's capability analysis
debugfs: Make debugfs_cancellation a capability struct
compiler-capability-analysis: Remove Sparse support
compiler-capability-analysis: Remove __cond_lock() function-like
helper
compiler-capability-analysis: Introduce header suppressions
compiler: Let data_race() imply disabled capability analysis
MAINTAINERS: Add entry for Capability Analysis
kfence: Enable capability analysis
kcov: Enable capability analysis
kcsan: Enable capability analysis
stackdepot: Enable capability analysis
rhashtable: Enable capability analysis
printk: Move locking annotation to printk.c
security/tomoyo: Enable capability analysis
crypto: Enable capability analysis
sched: Enable capability analysis for core.c and fair.c
.../dev-tools/capability-analysis.rst | 148 +++++
Documentation/dev-tools/index.rst | 1 +
Documentation/dev-tools/sparse.rst | 19 -
Documentation/mm/process_addrs.rst | 6 +-
MAINTAINERS | 11 +
Makefile | 1 +
crypto/Makefile | 2 +
crypto/acompress.c | 6 +-
crypto/algapi.c | 2 +
crypto/api.c | 1 +
crypto/crypto_engine.c | 2 +-
crypto/drbg.c | 5 +
crypto/internal.h | 2 +-
crypto/proc.c | 3 +
crypto/scompress.c | 24 +-
.../net/wireless/intel/iwlwifi/iwl-trans.c | 4 +-
.../net/wireless/intel/iwlwifi/iwl-trans.h | 6 +-
.../intel/iwlwifi/pcie/gen1_2/internal.h | 5 +-
.../intel/iwlwifi/pcie/gen1_2/trans.c | 4 +-
fs/dlm/lock.c | 2 +-
include/crypto/internal/acompress.h | 7 +-
include/crypto/internal/engine.h | 2 +-
include/linux/bit_spinlock.h | 24 +-
include/linux/cleanup.h | 17 +
include/linux/compiler-capability-analysis.h | 423 +++++++++++++
include/linux/compiler.h | 2 +
include/linux/compiler_types.h | 18 +-
include/linux/console.h | 4 +-
include/linux/debugfs.h | 12 +-
include/linux/kref.h | 2 +
include/linux/list_bl.h | 2 +
include/linux/local_lock.h | 45 +-
include/linux/local_lock_internal.h | 73 ++-
include/linux/lockdep.h | 12 +-
include/linux/mm.h | 33 +-
include/linux/mutex.h | 35 +-
include/linux/mutex_types.h | 4 +-
include/linux/rcupdate.h | 86 +--
include/linux/refcount.h | 6 +-
include/linux/rhashtable.h | 14 +-
include/linux/rwlock.h | 22 +-
include/linux/rwlock_api_smp.h | 43 +-
include/linux/rwlock_rt.h | 44 +-
include/linux/rwlock_types.h | 10 +-
include/linux/rwsem.h | 66 +-
include/linux/sched.h | 6 +-
include/linux/sched/signal.h | 16 +-
include/linux/sched/task.h | 5 +-
include/linux/sched/wake_q.h | 3 +
include/linux/seqlock.h | 24 +
include/linux/seqlock_types.h | 5 +-
include/linux/spinlock.h | 89 ++-
include/linux/spinlock_api_smp.h | 34 +-
include/linux/spinlock_api_up.h | 112 +++-
include/linux/spinlock_rt.h | 37 +-
include/linux/spinlock_types.h | 10 +-
include/linux/spinlock_types_raw.h | 5 +-
include/linux/srcu.h | 60 +-
include/linux/srcutiny.h | 4 +
include/linux/srcutree.h | 6 +-
include/linux/ww_mutex.h | 22 +-
kernel/Makefile | 2 +
kernel/kcov.c | 36 +-
kernel/kcsan/Makefile | 2 +
kernel/kcsan/report.c | 11 +-
kernel/printk/printk.c | 2 +
kernel/sched/Makefile | 3 +
kernel/sched/core.c | 89 ++-
kernel/sched/fair.c | 9 +-
kernel/sched/sched.h | 110 +++-
kernel/signal.c | 4 +-
kernel/time/posix-timers.c | 13 +-
lib/Kconfig.debug | 45 ++
lib/Makefile | 6 +
lib/dec_and_lock.c | 8 +-
lib/rhashtable.c | 5 +-
lib/stackdepot.c | 20 +-
lib/test_capability-analysis.c | 596 ++++++++++++++++++
mm/kfence/Makefile | 2 +
mm/kfence/core.c | 20 +-
mm/kfence/kfence.h | 14 +-
mm/kfence/report.c | 4 +-
mm/memory.c | 4 +-
mm/pgtable-generic.c | 19 +-
net/ipv4/tcp_sigpool.c | 2 +-
scripts/Makefile.capability-analysis | 11 +
scripts/Makefile.lib | 10 +
scripts/capability-analysis-suppression.txt | 33 +
scripts/checkpatch.pl | 8 +
security/tomoyo/Makefile | 2 +
security/tomoyo/common.c | 52 +-
security/tomoyo/common.h | 77 +--
security/tomoyo/domain.c | 1 +
security/tomoyo/environ.c | 1 +
security/tomoyo/file.c | 5 +
security/tomoyo/gc.c | 28 +-
security/tomoyo/mount.c | 2 +
security/tomoyo/network.c | 3 +
tools/include/linux/compiler_types.h | 2 -
99 files changed, 2370 insertions(+), 589 deletions(-)
create mode 100644 Documentation/dev-tools/capability-analysis.rst
create mode 100644 include/linux/compiler-capability-analysis.h
create mode 100644 lib/test_capability-analysis.c
create mode 100644 scripts/Makefile.capability-analysis
create mode 100644 scripts/capability-analysis-suppression.txt
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply
* [PATCH v3 01/35] compiler_types: Move lock checking attributes to compiler-capability-analysis.h
From: Marco Elver @ 2025-09-18 13:59 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett,
Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn,
Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor,
Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa,
Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long,
kasan-dev, linux-crypto, linux-doc, linux-kbuild, linux-kernel,
linux-mm, linux-security-module, linux-sparse, llvm, rcu
In-Reply-To: <20250918140451.1289454-1-elver@google.com>
The conditional definition of lock checking macros and attributes is
about to become more complex. Factor them out into their own header for
better readability, and to make it obvious which features are supported
by which mode (currently only Sparse). This is the first step towards
generalizing towards "capability analysis".
No functional change intended.
Signed-off-by: Marco Elver <elver@google.com>
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
---
include/linux/compiler-capability-analysis.h | 32 ++++++++++++++++++++
include/linux/compiler_types.h | 18 ++---------
2 files changed, 34 insertions(+), 16 deletions(-)
create mode 100644 include/linux/compiler-capability-analysis.h
diff --git a/include/linux/compiler-capability-analysis.h b/include/linux/compiler-capability-analysis.h
new file mode 100644
index 000000000000..7546ddb83f86
--- /dev/null
+++ b/include/linux/compiler-capability-analysis.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Macros and attributes for compiler-based static capability analysis.
+ */
+
+#ifndef _LINUX_COMPILER_CAPABILITY_ANALYSIS_H
+#define _LINUX_COMPILER_CAPABILITY_ANALYSIS_H
+
+#ifdef __CHECKER__
+
+/* Sparse context/lock checking support. */
+# define __must_hold(x) __attribute__((context(x,1,1)))
+# define __acquires(x) __attribute__((context(x,0,1)))
+# define __cond_acquires(x) __attribute__((context(x,0,-1)))
+# define __releases(x) __attribute__((context(x,1,0)))
+# define __acquire(x) __context__(x,1)
+# define __release(x) __context__(x,-1)
+# define __cond_lock(x, c) ((c) ? ({ __acquire(x); 1; }) : 0)
+
+#else /* !__CHECKER__ */
+
+# define __must_hold(x)
+# define __acquires(x)
+# define __cond_acquires(x)
+# define __releases(x)
+# define __acquire(x) (void)0
+# define __release(x) (void)0
+# define __cond_lock(x, c) (c)
+
+#endif /* __CHECKER__ */
+
+#endif /* _LINUX_COMPILER_CAPABILITY_ANALYSIS_H */
diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h
index 16755431fc11..c24e60e75f36 100644
--- a/include/linux/compiler_types.h
+++ b/include/linux/compiler_types.h
@@ -24,6 +24,8 @@
# define BTF_TYPE_TAG(value) /* nothing */
#endif
+#include <linux/compiler-capability-analysis.h>
+
/* sparse defines __CHECKER__; see Documentation/dev-tools/sparse.rst */
#ifdef __CHECKER__
/* address spaces */
@@ -34,14 +36,6 @@
# define __rcu __attribute__((noderef, address_space(__rcu)))
static inline void __chk_user_ptr(const volatile void __user *ptr) { }
static inline void __chk_io_ptr(const volatile void __iomem *ptr) { }
-/* context/locking */
-# define __must_hold(x) __attribute__((context(x,1,1)))
-# define __acquires(x) __attribute__((context(x,0,1)))
-# define __cond_acquires(x) __attribute__((context(x,0,-1)))
-# define __releases(x) __attribute__((context(x,1,0)))
-# define __acquire(x) __context__(x,1)
-# define __release(x) __context__(x,-1)
-# define __cond_lock(x,c) ((c) ? ({ __acquire(x); 1; }) : 0)
/* other */
# define __force __attribute__((force))
# define __nocast __attribute__((nocast))
@@ -62,14 +56,6 @@ static inline void __chk_io_ptr(const volatile void __iomem *ptr) { }
# define __chk_user_ptr(x) (void)0
# define __chk_io_ptr(x) (void)0
-/* context/locking */
-# define __must_hold(x)
-# define __acquires(x)
-# define __cond_acquires(x)
-# define __releases(x)
-# define __acquire(x) (void)0
-# define __release(x) (void)0
-# define __cond_lock(x,c) (c)
/* other */
# define __force
# define __nocast
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply related
* [PATCH v3 02/35] compiler-capability-analysis: Add infrastructure for Clang's capability analysis
From: Marco Elver @ 2025-09-18 13:59 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett,
Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn,
Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor,
Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa,
Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long,
kasan-dev, linux-crypto, linux-doc, linux-kbuild, linux-kernel,
linux-mm, linux-security-module, linux-sparse, llvm, rcu
In-Reply-To: <20250918140451.1289454-1-elver@google.com>
Capability analysis is a C language extension, which enables statically
checking that user-definable "capabilities" are acquired and released where
required. An obvious application is lock-safety checking for the kernel's
various synchronization primitives (each of which represents a "capability"),
and checking that locking rules are not violated.
Clang originally called the feature "Thread Safety Analysis" [1], with
some terminology still using the thread-safety-analysis-only names. This
was later changed and the feature became more flexible, gaining the
ability to define custom "capabilities". Its foundations can be found in
"capability systems", used to specify the permissibility of operations
to depend on some capability being held (or not held).
[1] https://clang.llvm.org/docs/ThreadSafetyAnalysis.html
[2] https://www.cs.cornell.edu/talc/papers/capabilities.pdf
Because the feature is not just able to express capabilities related to
synchronization primitives, the naming chosen for the kernel departs
from Clang's initial "Thread Safety" nomenclature and refers to the
feature as "Capability Analysis" to avoid confusion. The implementation
still makes references to the older terminology in some places, such as
`-Wthread-safety` being the warning enabled option that also still
appears in diagnostic messages.
See more details in the kernel-doc documentation added in this and the
subsequent changes.
Clang version 22+ is required.
Signed-off-by: Marco Elver <elver@google.com>
---
v3:
* Require Clang 22 or later (reentrant capabilities, basic alias analysis).
* Rename __assert_cap/__asserts_cap -> __assume_cap/__assumes_cap (suggested by Peter).
* Add __acquire_ret and __acquire_shared_ret helper macros - can be used
to define function-like macros that return objects which contains a
held capabilities. Works now because of capability alias analysis.
* Add capability_unsafe_alias() helper, where the analysis rightfully
points out we're doing strange things with aliases but we don't care.
* Support multi-argument attributes.
v2:
* New -Wthread-safety feature rename to -Wthread-safety-pointer (was
-Wthread-safety-addressof).
* Introduce __capability_unsafe() function attribute.
* Rename __var_guarded_by to simply __guarded_by. The initial idea was
to be explicit if the variable or pointed-to data is guarded by, but
having a shorter attribute name is likely better long-term.
* Rename __ref_guarded_by to __pt_guarded_by (pointed-to guarded by).
---
Makefile | 1 +
include/linux/compiler-capability-analysis.h | 449 ++++++++++++++++++-
lib/Kconfig.debug | 31 ++
scripts/Makefile.capability-analysis | 7 +
scripts/Makefile.lib | 10 +
5 files changed, 491 insertions(+), 7 deletions(-)
create mode 100644 scripts/Makefile.capability-analysis
diff --git a/Makefile b/Makefile
index cf37b9407821..2c91730e513b 100644
--- a/Makefile
+++ b/Makefile
@@ -1096,6 +1096,7 @@ include-$(CONFIG_RANDSTRUCT) += scripts/Makefile.randstruct
include-$(CONFIG_KSTACK_ERASE) += scripts/Makefile.kstack_erase
include-$(CONFIG_AUTOFDO_CLANG) += scripts/Makefile.autofdo
include-$(CONFIG_PROPELLER_CLANG) += scripts/Makefile.propeller
+include-$(CONFIG_WARN_CAPABILITY_ANALYSIS) += scripts/Makefile.capability-analysis
include-$(CONFIG_GCC_PLUGINS) += scripts/Makefile.gcc-plugins
include $(addprefix $(srctree)/, $(include-y))
diff --git a/include/linux/compiler-capability-analysis.h b/include/linux/compiler-capability-analysis.h
index 7546ddb83f86..6f3f185478bc 100644
--- a/include/linux/compiler-capability-analysis.h
+++ b/include/linux/compiler-capability-analysis.h
@@ -6,27 +6,462 @@
#ifndef _LINUX_COMPILER_CAPABILITY_ANALYSIS_H
#define _LINUX_COMPILER_CAPABILITY_ANALYSIS_H
+#if defined(WARN_CAPABILITY_ANALYSIS)
+
+/*
+ * The below attributes are used to define new capability types. Internal only.
+ */
+# define __cap_type(name) __attribute__((capability(#name)))
+# define __reentrant_cap __attribute__((reentrant_capability))
+# define __acquires_cap(...) __attribute__((acquire_capability(__VA_ARGS__)))
+# define __acquires_shared_cap(...) __attribute__((acquire_shared_capability(__VA_ARGS__)))
+# define __try_acquires_cap(ret, var) __attribute__((try_acquire_capability(ret, var)))
+# define __try_acquires_shared_cap(ret, var) __attribute__((try_acquire_shared_capability(ret, var)))
+# define __releases_cap(...) __attribute__((release_capability(__VA_ARGS__)))
+# define __releases_shared_cap(...) __attribute__((release_shared_capability(__VA_ARGS__)))
+# define __assumes_cap(...) __attribute__((assert_capability(__VA_ARGS__)))
+# define __assumes_shared_cap(...) __attribute__((assert_shared_capability(__VA_ARGS__)))
+# define __returns_cap(var) __attribute__((lock_returned(var)))
+
+/*
+ * The below are used to annotate code being checked. Internal only.
+ */
+# define __excludes_cap(...) __attribute__((locks_excluded(__VA_ARGS__)))
+# define __requires_cap(...) __attribute__((requires_capability(__VA_ARGS__)))
+# define __requires_shared_cap(...) __attribute__((requires_shared_capability(__VA_ARGS__)))
+
+/**
+ * __guarded_by - struct member and globals attribute, declares variable
+ * protected by capability
+ *
+ * Declares that the struct member or global variable must be guarded by the
+ * given capabilities. Read operations on the data require shared access,
+ * while write operations require exclusive access.
+ *
+ * .. code-block:: c
+ *
+ * struct some_state {
+ * spinlock_t lock;
+ * long counter __guarded_by(&lock);
+ * };
+ */
+# define __guarded_by(...) __attribute__((guarded_by(__VA_ARGS__)))
+
+/**
+ * __pt_guarded_by - struct member and globals attribute, declares pointed-to
+ * data is protected by capability
+ *
+ * Declares that the data pointed to by the struct member pointer or global
+ * pointer must be guarded by the given capabilities. Read operations on the
+ * data require shared access, while write operations require exclusive access.
+ *
+ * .. code-block:: c
+ *
+ * struct some_state {
+ * spinlock_t lock;
+ * long *counter __pt_guarded_by(&lock);
+ * };
+ */
+# define __pt_guarded_by(...) __attribute__((pt_guarded_by(__VA_ARGS__)))
+
+/**
+ * struct_with_capability() - declare or define a capability struct
+ * @name: struct name
+ *
+ * Helper to declare or define a struct type with capability of the same name.
+ *
+ * .. code-block:: c
+ *
+ * struct_with_capability(my_handle) {
+ * int foo;
+ * long bar;
+ * };
+ *
+ * struct some_state {
+ * ...
+ * };
+ * // ... declared elsewhere ...
+ * struct_with_capability(some_state);
+ *
+ * Note: The implementation defines several helper functions that can acquire,
+ * release, and assert the capability.
+ */
+# define struct_with_capability(name, ...) \
+ struct __cap_type(name) __VA_ARGS__ name; \
+ static __always_inline void __acquire_cap(const struct name *var) \
+ __attribute__((overloadable)) __no_capability_analysis __acquires_cap(var) { } \
+ static __always_inline void __acquire_shared_cap(const struct name *var) \
+ __attribute__((overloadable)) __no_capability_analysis __acquires_shared_cap(var) { } \
+ static __always_inline bool __try_acquire_cap(const struct name *var, bool ret) \
+ __attribute__((overloadable)) __no_capability_analysis __try_acquires_cap(1, var) \
+ { return ret; } \
+ static __always_inline bool __try_acquire_shared_cap(const struct name *var, bool ret) \
+ __attribute__((overloadable)) __no_capability_analysis __try_acquires_shared_cap(1, var) \
+ { return ret; } \
+ static __always_inline void __release_cap(const struct name *var) \
+ __attribute__((overloadable)) __no_capability_analysis __releases_cap(var) { } \
+ static __always_inline void __release_shared_cap(const struct name *var) \
+ __attribute__((overloadable)) __no_capability_analysis __releases_shared_cap(var) { } \
+ static __always_inline void __assume_cap(const struct name *var) \
+ __attribute__((overloadable)) __assumes_cap(var) { } \
+ static __always_inline void __assume_shared_cap(const struct name *var) \
+ __attribute__((overloadable)) __assumes_shared_cap(var) { } \
+ struct name
+
+/**
+ * disable_capability_analysis() - disables capability analysis
+ *
+ * Disables capability analysis. Must be paired with a later
+ * enable_capability_analysis().
+ */
+# define disable_capability_analysis() \
+ __diag_push(); \
+ __diag_ignore_all("-Wunknown-warning-option", "") \
+ __diag_ignore_all("-Wthread-safety", "") \
+ __diag_ignore_all("-Wthread-safety-pointer", "")
+
+/**
+ * enable_capability_analysis() - re-enables capability analysis
+ *
+ * Re-enables capability analysis. Must be paired with a prior
+ * disable_capability_analysis().
+ */
+# define enable_capability_analysis() __diag_pop()
+
+/**
+ * __no_capability_analysis - function attribute, disables capability analysis
+ *
+ * Function attribute denoting that capability analysis is disabled for the
+ * whole function. Prefer use of `capability_unsafe()` where possible.
+ */
+# define __no_capability_analysis __attribute__((no_thread_safety_analysis))
+
+#else /* !WARN_CAPABILITY_ANALYSIS */
+
+# define __cap_type(name)
+# define __reentrant_cap
+# define __acquires_cap(...)
+# define __acquires_shared_cap(...)
+# define __try_acquires_cap(ret, var)
+# define __try_acquires_shared_cap(ret, var)
+# define __releases_cap(...)
+# define __releases_shared_cap(...)
+# define __assumes_cap(...)
+# define __assumes_shared_cap(...)
+# define __returns_cap(var)
+# define __guarded_by(...)
+# define __pt_guarded_by(...)
+# define __excludes_cap(...)
+# define __requires_cap(...)
+# define __requires_shared_cap(...)
+# define __acquire_cap(var) do { } while (0)
+# define __acquire_shared_cap(var) do { } while (0)
+# define __try_acquire_cap(var, ret) (ret)
+# define __try_acquire_shared_cap(var, ret) (ret)
+# define __release_cap(var) do { } while (0)
+# define __release_shared_cap(var) do { } while (0)
+# define __assume_cap(var) do { (void)(var); } while (0)
+# define __assume_shared_cap(var) do { (void)(var); } while (0)
+# define struct_with_capability(name, ...) struct __VA_ARGS__ name
+# define disable_capability_analysis()
+# define enable_capability_analysis()
+# define __no_capability_analysis
+
+#endif /* WARN_CAPABILITY_ANALYSIS */
+
+/**
+ * capability_unsafe() - disable capability checking for contained code
+ *
+ * Disables capability checking for contained statements or expression.
+ *
+ * .. code-block:: c
+ *
+ * struct some_data {
+ * spinlock_t lock;
+ * int counter __guarded_by(&lock);
+ * };
+ *
+ * int foo(struct some_data *d)
+ * {
+ * // ...
+ * // other code that is still checked ...
+ * // ...
+ * return capability_unsafe(d->counter);
+ * }
+ */
+#define capability_unsafe(...) \
+({ \
+ disable_capability_analysis(); \
+ __VA_ARGS__; \
+ enable_capability_analysis() \
+})
+
+/**
+ * __capability_unsafe() - function attribute, disable capability checking
+ * @comment: comment explaining why opt-out is safe
+ *
+ * Function attribute denoting that capability analysis is disabled for the
+ * whole function. Forces adding an inline comment as argument.
+ */
+#define __capability_unsafe(comment) __no_capability_analysis
+
+/**
+ * capability_unsafe_alias() - helper to insert a capability "alias barrier"
+ * @p: pointer aliasing a capability or object containing capabilities
+ *
+ * No-op function that acts as a "capability alias barrier", where the analysis
+ * rightfully detects that we're switching aliases, but the switch is considered
+ * safe but beyond the analysis reasoning abilities.
+ *
+ * This should be inserted before the first use of such an alias.
+ *
+ * Implementation Note: The compiler ignores aliases that may be reassigned but
+ * their value cannot be determined (e.g. when passing a non-const pointer to an
+ * alias as a function argument).
+ */
+#define capability_unsafe_alias(p) _capability_unsafe_alias((void **)&(p))
+static inline void _capability_unsafe_alias(void **p) { }
+
+/**
+ * token_capability() - declare an abstract global capability instance
+ * @name: token capability name
+ *
+ * Helper that declares an abstract global capability instance @name that can be
+ * used as a token capability, but not backed by a real data structure (linker
+ * error if accidentally referenced). The type name is `__capability_@name`.
+ */
+#define token_capability(name, ...) \
+ struct_with_capability(__capability_##name, ##__VA_ARGS__) {}; \
+ extern const struct __capability_##name *name
+
+/**
+ * token_capability_instance() - declare another instance of a global capability
+ * @cap: token capability previously declared with token_capability()
+ * @name: name of additional global capability instance
+ *
+ * Helper that declares an additional instance @name of the same token
+ * capability class @name. This is helpful where multiple related token
+ * capabilities are declared, as it also allows using the same underlying type
+ * (`__capability_@cap`) as function arguments.
+ */
+#define token_capability_instance(cap, name) \
+ extern const struct __capability_##cap *name
+
+/*
+ * Common keywords for static capability analysis. Both Clang's capability
+ * analysis and Sparse's context tracking are currently supported.
+ */
#ifdef __CHECKER__
/* Sparse context/lock checking support. */
# define __must_hold(x) __attribute__((context(x,1,1)))
+# define __must_not_hold(x)
# define __acquires(x) __attribute__((context(x,0,1)))
# define __cond_acquires(x) __attribute__((context(x,0,-1)))
# define __releases(x) __attribute__((context(x,1,0)))
# define __acquire(x) __context__(x,1)
# define __release(x) __context__(x,-1)
# define __cond_lock(x, c) ((c) ? ({ __acquire(x); 1; }) : 0)
+/* For Sparse, there's no distinction between exclusive and shared locks. */
+# define __must_hold_shared __must_hold
+# define __acquires_shared __acquires
+# define __cond_acquires_shared __cond_acquires
+# define __releases_shared __releases
+# define __acquire_shared __acquire
+# define __release_shared __release
+# define __cond_lock_shared __cond_acquire
#else /* !__CHECKER__ */
-# define __must_hold(x)
-# define __acquires(x)
-# define __cond_acquires(x)
-# define __releases(x)
-# define __acquire(x) (void)0
-# define __release(x) (void)0
-# define __cond_lock(x, c) (c)
+/**
+ * __must_hold() - function attribute, caller must hold exclusive capability
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the caller must hold the given capability
+ * instance @x exclusively.
+ */
+# define __must_hold(x) __requires_cap(x)
+
+/**
+ * __must_not_hold() - function attribute, caller must not hold capability
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the caller must not hold the given
+ * capability instance @x.
+ */
+# define __must_not_hold(x) __excludes_cap(x)
+
+/**
+ * __acquires() - function attribute, function acquires capability exclusively
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the function acquires the given
+ * capability instance @x exclusively, but does not release it.
+ */
+# define __acquires(x) __acquires_cap(x)
+
+/**
+ * __cond_acquires() - function attribute, function conditionally
+ * acquires a capability exclusively
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the function conditionally acquires the
+ * given capability instance @x exclusively, but does not release it.
+ */
+# define __cond_acquires(x) __try_acquires_cap(1, x)
+
+/**
+ * __releases() - function attribute, function releases a capability exclusively
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the function releases the given capability
+ * instance @x exclusively. The capability must be held on entry.
+ */
+# define __releases(x) __releases_cap(x)
+
+/**
+ * __acquire() - function to acquire capability exclusively
+ * @x: capability instance pointer
+ *
+ * No-op function that acquires the given capability instance @x exclusively.
+ */
+# define __acquire(x) __acquire_cap(x)
+
+/**
+ * __release() - function to release capability exclusively
+ * @x: capability instance pointer
+ *
+ * No-op function that releases the given capability instance @x.
+ */
+# define __release(x) __release_cap(x)
+
+/**
+ * __cond_lock() - function that conditionally acquires a capability
+ * exclusively
+ * @x: capability instance pinter
+ * @c: boolean expression
+ *
+ * Return: result of @c
+ *
+ * No-op function that conditionally acquires capability instance @x
+ * exclusively, if the boolean expression @c is true. The result of @c is the
+ * return value, to be able to create a capability-enabled interface; for
+ * example:
+ *
+ * .. code-block:: c
+ *
+ * #define spin_trylock(l) __cond_lock(&lock, _spin_trylock(&lock))
+ */
+# define __cond_lock(x, c) __try_acquire_cap(x, c)
+
+/**
+ * __must_hold_shared() - function attribute, caller must hold shared capability
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the caller must hold the given capability
+ * instance @x with shared access.
+ */
+# define __must_hold_shared(x) __requires_shared_cap(x)
+
+/**
+ * __acquires_shared() - function attribute, function acquires capability shared
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the function acquires the given
+ * capability instance @x with shared access, but does not release it.
+ */
+# define __acquires_shared(x) __acquires_shared_cap(x)
+
+/**
+ * __cond_acquires_shared() - function attribute, function conditionally
+ * acquires a capability shared
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the function conditionally acquires the
+ * given capability instance @x with shared access, but does not release it.
+ */
+# define __cond_acquires_shared(x) __try_acquires_shared_cap(1, x)
+
+/**
+ * __releases_shared() - function attribute, function releases a
+ * capability shared
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the function releases the given capability
+ * instance @x with shared access. The capability must be held on entry.
+ */
+# define __releases_shared(x) __releases_shared_cap(x)
+
+/**
+ * __acquire_shared() - function to acquire capability shared
+ * @x: capability instance pointer
+ *
+ * No-op function that acquires the given capability instance @x with shared
+ * access.
+ */
+# define __acquire_shared(x) __acquire_shared_cap(x)
+
+/**
+ * __release_shared() - function to release capability shared
+ * @x: capability instance pointer
+ *
+ * No-op function that releases the given capability instance @x with shared
+ * access.
+ */
+# define __release_shared(x) __release_shared_cap(x)
+
+/**
+ * __cond_lock_shared() - function that conditionally acquires a capability
+ * shared
+ * @x: capability instance pinter
+ * @c: boolean expression
+ *
+ * Return: result of @c
+ *
+ * No-op function that conditionally acquires capability instance @x with shared
+ * access, if the boolean expression @c is true. The result of @c is the return
+ * value, to be able to create a capability-enabled interface.
+ */
+# define __cond_lock_shared(x, c) __try_acquire_shared_cap(x, c)
#endif /* __CHECKER__ */
+/**
+ * __acquire_ret() - helper to acquire capability of return value
+ * @call: call expression
+ * @ret_expr: acquire expression that uses __ret
+ */
+#define __acquire_ret(call, ret_expr) \
+ ({ \
+ __auto_type __ret = call; \
+ __acquire(ret_expr); \
+ __ret; \
+ })
+
+/**
+ * __acquire_shared_ret() - helper to acquire capability shared of return value
+ * @call: call expression
+ * @ret_expr: acquire shared expression that uses __ret
+ */
+#define __acquire_shared_ret(call, ret_expr) \
+ ({ \
+ __auto_type __ret = call; \
+ __acquire_shared(ret_expr); \
+ __ret; \
+ })
+
+/*
+ * Attributes to mark functions returning acquired capabilities. This is purely
+ * cosmetic to help readability, and should be used with the above macros as
+ * follows:
+ *
+ * struct foo { spinlock_t lock; ... };
+ * ...
+ * #define myfunc(...) __acquire_ret(_myfunc(__VA_ARGS__), &__ret->lock)
+ * struct foo *_myfunc(int bar) __acquires_ret;
+ * ...
+ */
+#define __acquires_ret __no_capability_analysis
+#define __acquires_shared_ret __no_capability_analysis
+
#endif /* _LINUX_COMPILER_CAPABILITY_ANALYSIS_H */
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index dc0e0c6ed075..57e09615f88d 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -613,6 +613,37 @@ config DEBUG_FORCE_WEAK_PER_CPU
To ensure that generic code follows the above rules, this
option forces all percpu variables to be defined as weak.
+config WARN_CAPABILITY_ANALYSIS
+ bool "Compiler capability-analysis warnings"
+ depends on CC_IS_CLANG && CLANG_VERSION >= 220000
+ # Branch profiling re-defines "if", which messes with the compiler's
+ # ability to analyze __cond_acquires(..), resulting in false positives.
+ depends on !TRACE_BRANCH_PROFILING
+ default y
+ help
+ Capability analysis is a C language extension, which enables
+ statically checking that user-definable "capabilities" are acquired
+ and released where required.
+
+ Clang's name of the feature ("Thread Safety Analysis") refers to
+ the original name of the feature; it was later expanded to be a
+ generic "Capability Analysis" framework.
+
+ Requires Clang 22 or later.
+
+ Produces warnings by default. Select CONFIG_WERROR if you wish to
+ turn these warnings into errors.
+
+config WARN_CAPABILITY_ANALYSIS_ALL
+ bool "Enable capability analysis for all source files"
+ depends on WARN_CAPABILITY_ANALYSIS
+ depends on EXPERT && !COMPILE_TEST
+ help
+ Enable tree-wide capability analysis. This is likely to produce a
+ large number of false positives - enable at your own risk.
+
+ If unsure, say N.
+
endmenu # "Compiler options"
menu "Generic Kernel Debugging Instruments"
diff --git a/scripts/Makefile.capability-analysis b/scripts/Makefile.capability-analysis
new file mode 100644
index 000000000000..e137751a4c9a
--- /dev/null
+++ b/scripts/Makefile.capability-analysis
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+capability-analysis-cflags := -DWARN_CAPABILITY_ANALYSIS \
+ -fexperimental-late-parse-attributes -Wthread-safety \
+ -Wthread-safety-pointer -Wthread-safety-beta
+
+export CFLAGS_CAPABILITY_ANALYSIS := $(capability-analysis-cflags)
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 1d581ba5df66..e0ac273bf9eb 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -105,6 +105,16 @@ _c_flags += $(if $(patsubst n%,, \
-D__KCSAN_INSTRUMENT_BARRIERS__)
endif
+#
+# Enable capability analysis flags only where explicitly opted in.
+# (depends on variables CAPABILITY_ANALYSIS_obj.o, CAPABILITY_ANALYSIS)
+#
+ifeq ($(CONFIG_WARN_CAPABILITY_ANALYSIS),y)
+_c_flags += $(if $(patsubst n%,, \
+ $(CAPABILITY_ANALYSIS_$(target-stem).o)$(CAPABILITY_ANALYSIS)$(if $(is-kernel-object),$(CONFIG_WARN_CAPABILITY_ANALYSIS_ALL))), \
+ $(CFLAGS_CAPABILITY_ANALYSIS))
+endif
+
#
# Enable AutoFDO build flags except some files or directories we don't want to
# enable (depends on variables AUTOFDO_PROFILE_obj.o and AUTOFDO_PROFILE).
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply related
* [PATCH v3 03/35] compiler-capability-analysis: Add test stub
From: Marco Elver @ 2025-09-18 13:59 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett,
Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn,
Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor,
Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa,
Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long,
kasan-dev, linux-crypto, linux-doc, linux-kbuild, linux-kernel,
linux-mm, linux-security-module, linux-sparse, llvm, rcu
In-Reply-To: <20250918140451.1289454-1-elver@google.com>
Add a simple test stub where we will add common supported patterns that
should not generate false positive of each new supported capability.
Signed-off-by: Marco Elver <elver@google.com>
---
lib/Kconfig.debug | 14 ++++++++++++++
lib/Makefile | 3 +++
lib/test_capability-analysis.c | 18 ++++++++++++++++++
3 files changed, 35 insertions(+)
create mode 100644 lib/test_capability-analysis.c
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 57e09615f88d..ac024861930f 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2813,6 +2813,20 @@ config LINEAR_RANGES_TEST
If unsure, say N.
+config CAPABILITY_ANALYSIS_TEST
+ bool "Compiler capability-analysis warnings test"
+ depends on EXPERT
+ help
+ This builds the test for compiler-based capability analysis. The test
+ does not add executable code to the kernel, but is meant to test that
+ common patterns supported by the analysis do not result in false
+ positive warnings.
+
+ When adding support for new capabilities, it is strongly recommended
+ to add supported patterns to this test.
+
+ If unsure, say N.
+
config CMDLINE_KUNIT_TEST
tristate "KUnit test for cmdline API" if !KUNIT_ALL_TESTS
depends on KUNIT
diff --git a/lib/Makefile b/lib/Makefile
index 392ff808c9b9..e677cb5cc777 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -332,4 +332,7 @@ obj-$(CONFIG_GENERIC_LIB_DEVMEM_IS_ALLOWED) += devmem_is_allowed.o
obj-$(CONFIG_FIRMWARE_TABLE) += fw_table.o
+CAPABILITY_ANALYSIS_test_capability-analysis.o := y
+obj-$(CONFIG_CAPABILITY_ANALYSIS_TEST) += test_capability-analysis.o
+
subdir-$(CONFIG_FORTIFY_SOURCE) += test_fortify
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
new file mode 100644
index 000000000000..a0adacce30ff
--- /dev/null
+++ b/lib/test_capability-analysis.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Compile-only tests for common patterns that should not generate false
+ * positive errors when compiled with Clang's capability analysis.
+ */
+
+#include <linux/build_bug.h>
+
+/*
+ * Test that helper macros work as expected.
+ */
+static void __used test_common_helpers(void)
+{
+ BUILD_BUG_ON(capability_unsafe(3) != 3); /* plain expression */
+ BUILD_BUG_ON(capability_unsafe((void)2; 3;) != 3); /* does not swallow semi-colon */
+ BUILD_BUG_ON(capability_unsafe((void)2, 3) != 3); /* does not swallow commas */
+ capability_unsafe(do { } while (0)); /* works with void statements */
+}
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply related
* [PATCH v3 04/35] Documentation: Add documentation for Compiler-Based Capability Analysis
From: Marco Elver @ 2025-09-18 13:59 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett,
Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn,
Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor,
Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa,
Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long,
kasan-dev, linux-crypto, linux-doc, linux-kbuild, linux-kernel,
linux-mm, linux-security-module, linux-sparse, llvm, rcu
In-Reply-To: <20250918140451.1289454-1-elver@google.com>
Adds documentation in Documentation/dev-tools/capability-analysis.rst,
and adds it to the index and cross-references from Sparse's document.
Signed-off-by: Marco Elver <elver@google.com>
---
v2:
* Remove cross-reference to Sparse, since we plan to remove Sparse
support anyway.
* Mention __no_capability_analysis should be avoided.
---
.../dev-tools/capability-analysis.rst | 147 ++++++++++++++++++
Documentation/dev-tools/index.rst | 1 +
2 files changed, 148 insertions(+)
create mode 100644 Documentation/dev-tools/capability-analysis.rst
diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
new file mode 100644
index 000000000000..1287f792f6cd
--- /dev/null
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -0,0 +1,147 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. Copyright (C) 2025, Google LLC.
+
+.. _capability-analysis:
+
+Compiler-Based Capability Analysis
+==================================
+
+Capability analysis is a C language extension, which enables statically
+checking that user-definable "capabilities" are acquired and released where
+required. An obvious application is lock-safety checking for the kernel's
+various synchronization primitives (each of which represents a "capability"),
+and checking that locking rules are not violated.
+
+The Clang compiler currently supports the full set of capability analysis
+features. To enable for Clang, configure the kernel with::
+
+ CONFIG_WARN_CAPABILITY_ANALYSIS=y
+
+The feature requires Clang 22 or later.
+
+The analysis is *opt-in by default*, and requires declaring which modules and
+subsystems should be analyzed in the respective `Makefile`::
+
+ CAPABILITY_ANALYSIS_mymodule.o := y
+
+Or for all translation units in the directory::
+
+ CAPABILITY_ANALYSIS := y
+
+It is possible to enable the analysis tree-wide, however, which will result in
+numerous false positive warnings currently and is *not* generally recommended::
+
+ CONFIG_WARN_CAPABILITY_ANALYSIS_ALL=y
+
+Programming Model
+-----------------
+
+The below describes the programming model around using capability-enabled
+types.
+
+.. note::
+ Enabling capability analysis can be seen as enabling a dialect of Linux C with
+ a Capability System. Some valid patterns involving complex control-flow are
+ constrained (such as conditional acquisition and later conditional release
+ in the same function, or returning pointers to capabilities from functions.
+
+Capability analysis is a way to specify permissibility of operations to depend
+on capabilities being held (or not held). Typically we are interested in
+protecting data and code by requiring some capability to be held, for example a
+specific lock. The analysis ensures that the caller cannot perform the
+operation without holding the appropriate capability.
+
+Capabilities are associated with named structs, along with functions that
+operate on capability-enabled struct instances to acquire and release the
+associated capability.
+
+Capabilities can be held either exclusively or shared. This mechanism allows
+assign more precise privileges when holding a capability, typically to
+distinguish where a thread may only read (shared) or also write (exclusive) to
+guarded data.
+
+The set of capabilities that are actually held by a given thread at a given
+point in program execution is a run-time concept. The static analysis works by
+calculating an approximation of that set, called the capability environment.
+The capability environment is calculated for every program point, and describes
+the set of capabilities that are statically known to be held, or not held, at
+that particular point. This environment is a conservative approximation of the
+full set of capabilities that will actually held by a thread at run-time.
+
+More details are also documented `here
+<https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`_.
+
+.. note::
+ Clang's analysis explicitly does not infer capabilities acquired or released
+ by inline functions. It requires explicit annotations to (a) assert that
+ it's not a bug if a capability is released or acquired, and (b) to retain
+ consistency between inline and non-inline function declarations.
+
+Supported Kernel Primitives
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. Currently the following synchronization primitives are supported:
+
+For capabilities with an initialization function (e.g., `spin_lock_init()`),
+calling this function on the capability instance before initializing any
+guarded members or globals prevents the compiler from issuing warnings about
+unguarded initialization.
+
+Lockdep assertions, such as `lockdep_assert_held()`, inform the compiler's
+capability analysis that the associated synchronization primitive is held after
+the assertion. This avoids false positives in complex control-flow scenarios
+and encourages the use of Lockdep where static analysis is limited. For
+example, this is useful when a function doesn't *always* require a lock, making
+`__must_hold()` inappropriate.
+
+Keywords
+~~~~~~~~
+
+.. kernel-doc:: include/linux/compiler-capability-analysis.h
+ :identifiers: struct_with_capability
+ token_capability token_capability_instance
+ __guarded_by __pt_guarded_by
+ __must_hold
+ __must_not_hold
+ __acquires
+ __cond_acquires
+ __releases
+ __must_hold_shared
+ __acquires_shared
+ __cond_acquires_shared
+ __releases_shared
+ __acquire
+ __release
+ __cond_lock
+ __acquire_shared
+ __release_shared
+ __cond_lock_shared
+ capability_unsafe
+ __capability_unsafe
+ disable_capability_analysis enable_capability_analysis
+
+.. note::
+ The function attribute `__no_capability_analysis` is reserved for internal
+ implementation of capability-enabled primitives, and should be avoided in
+ normal code.
+
+Background
+----------
+
+Clang originally called the feature `Thread Safety Analysis
+<https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`_, with some
+terminology still using the thread-safety-analysis-only names. This was later
+changed and the feature became more flexible, gaining the ability to define
+custom "capabilities".
+
+Indeed, its foundations can be found in `capability systems
+<https://www.cs.cornell.edu/talc/papers/capabilities.pdf>`_, used to specify
+the permissibility of operations to depend on some capability being held (or
+not held).
+
+Because the feature is not just able to express capabilities related to
+synchronization primitives, the naming chosen for the kernel departs from
+Clang's initial "Thread Safety" nomenclature and refers to the feature as
+"Capability Analysis" to avoid confusion. The implementation still makes
+references to the older terminology in some places, such as `-Wthread-safety`
+being the warning option that also still appears in diagnostic messages.
diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst
index 65c54b27a60b..62ac23f797cd 100644
--- a/Documentation/dev-tools/index.rst
+++ b/Documentation/dev-tools/index.rst
@@ -18,6 +18,7 @@ Documentation/process/debugging/index.rst
:maxdepth: 2
testing-overview
+ capability-analysis
checkpatch
clang-format
coccinelle
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply related
* [PATCH v3 05/35] checkpatch: Warn about capability_unsafe() without comment
From: Marco Elver @ 2025-09-18 13:59 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett,
Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn,
Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor,
Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa,
Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long,
kasan-dev, linux-crypto, linux-doc, linux-kbuild, linux-kernel,
linux-mm, linux-security-module, linux-sparse, llvm, rcu
In-Reply-To: <20250918140451.1289454-1-elver@google.com>
Warn about applications of capability_unsafe() without a comment, to
encourage documenting the reasoning behind why it was deemed safe.
Signed-off-by: Marco Elver <elver@google.com>
---
scripts/checkpatch.pl | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index e722dd6fa8ef..532075e67a96 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -6717,6 +6717,14 @@ sub process {
}
}
+# check for capability_unsafe without a comment.
+ if ($line =~ /\bcapability_unsafe\b/) {
+ if (!ctx_has_comment($first_line, $linenr)) {
+ WARN("CAPABILITY_UNSAFE",
+ "capability_unsafe without comment\n" . $herecurr);
+ }
+ }
+
# check of hardware specific defines
if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) {
CHK("ARCH_DEFINES",
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply related
* [PATCH v3 06/35] cleanup: Basic compatibility with capability analysis
From: Marco Elver @ 2025-09-18 13:59 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett,
Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn,
Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor,
Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa,
Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long,
kasan-dev, linux-crypto, linux-doc, linux-kbuild, linux-kernel,
linux-mm, linux-security-module, linux-sparse, llvm, rcu
In-Reply-To: <20250918140451.1289454-1-elver@google.com>
Introduce basic compatibility with cleanup.h infrastructure: introduce
DECLARE_LOCK_GUARD_*_ATTRS() helpers to add attributes to constructors
and destructors respectively.
Note: Due to the scoped cleanup helpers used for lock guards wrapping
acquire and release around their own constructors/destructors that store
pointers to the passed locks in a separate struct, we currently cannot
accurately annotate *destructors* which lock was released. While it's
possible to annotate the constructor to say which lock was acquired,
that alone would result in false positives claiming the lock was not
released on function return.
Instead, to avoid false positives, we can claim that the constructor
"assumes" that the taken lock is held via __assumes_cap().
This will ensure we can still benefit from the analysis where scoped
guards are used to protect access to guarded variables, while avoiding
false positives. The only downside are false negatives where we might
accidentally lock the same lock again:
raw_spin_lock(&my_lock);
...
guard(raw_spinlock)(&my_lock); // no warning
Arguably, lockdep will immediately catch issues like this.
While Clang's analysis supports scoped guards in C++ [1], there's no way
to apply this to C right now. Better support for Linux's scoped guard
design could be added in future if deemed critical.
[1] https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#scoped-capability
Signed-off-by: Marco Elver <elver@google.com>
---
v3:
* Add *_ATTRS helpers instead of implicit __assumes_cap (suggested by Peter)
* __assert -> __assume rename
---
include/linux/cleanup.h | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/include/linux/cleanup.h b/include/linux/cleanup.h
index 2573585b7f06..54fc70d8da27 100644
--- a/include/linux/cleanup.h
+++ b/include/linux/cleanup.h
@@ -274,16 +274,21 @@ const volatile void * __must_check_fn(const volatile void *val)
#define DEFINE_CLASS(_name, _type, _exit, _init, _init_args...) \
typedef _type class_##_name##_t; \
+typedef _type lock_##_name##_t; \
static inline void class_##_name##_destructor(_type *p) \
+ __no_capability_analysis \
{ _type _T = *p; _exit; } \
static inline _type class_##_name##_constructor(_init_args) \
+ __no_capability_analysis \
{ _type t = _init; return t; }
#define EXTEND_CLASS(_name, ext, _init, _init_args...) \
+typedef lock_##_name##_t lock_##_name##ext##_t; \
typedef class_##_name##_t class_##_name##ext##_t; \
static inline void class_##_name##ext##_destructor(class_##_name##_t *p)\
{ class_##_name##_destructor(p); } \
static inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \
+ __no_capability_analysis \
{ class_##_name##_t t = _init; return t; }
#define CLASS(_name, var) \
@@ -461,12 +466,14 @@ _label: \
*/
#define __DEFINE_UNLOCK_GUARD(_name, _type, _unlock, ...) \
+typedef _type lock_##_name##_t; \
typedef struct { \
_type *lock; \
__VA_ARGS__; \
} class_##_name##_t; \
\
static inline void class_##_name##_destructor(class_##_name##_t *_T) \
+ __no_capability_analysis \
{ \
if (!__GUARD_IS_ERR(_T->lock)) { _unlock; } \
} \
@@ -475,6 +482,7 @@ __DEFINE_GUARD_LOCK_PTR(_name, &_T->lock)
#define __DEFINE_LOCK_GUARD_1(_name, _type, _lock) \
static inline class_##_name##_t class_##_name##_constructor(_type *l) \
+ __no_capability_analysis \
{ \
class_##_name##_t _t = { .lock = l }, *_T = &_t; \
_lock; \
@@ -483,6 +491,7 @@ static inline class_##_name##_t class_##_name##_constructor(_type *l) \
#define __DEFINE_LOCK_GUARD_0(_name, _lock) \
static inline class_##_name##_t class_##_name##_constructor(void) \
+ __no_capability_analysis \
{ \
class_##_name##_t _t = { .lock = (void*)1 }, \
*_T __maybe_unused = &_t; \
@@ -490,6 +499,14 @@ static inline class_##_name##_t class_##_name##_constructor(void) \
return _t; \
}
+#define DECLARE_LOCK_GUARD_0_ATTRS(_name, _lock, _unlock) \
+static inline class_##_name##_t class_##_name##_constructor(void) _lock;\
+static inline void class_##_name##_destructor(class_##_name##_t *_T) _unlock;
+
+#define DECLARE_LOCK_GUARD_1_ATTRS(_name, _lock, _unlock) \
+static inline class_##_name##_t class_##_name##_constructor(lock_##_name##_t *_T) _lock;\
+static inline void class_##_name##_destructor(class_##_name##_t *_T) _unlock;
+
#define DEFINE_LOCK_GUARD_1(_name, _type, _lock, _unlock, ...) \
__DEFINE_CLASS_IS_CONDITIONAL(_name, false); \
__DEFINE_UNLOCK_GUARD(_name, _type, _unlock, __VA_ARGS__) \
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply related
* [PATCH v3 07/35] lockdep: Annotate lockdep assertions for capability analysis
From: Marco Elver @ 2025-09-18 13:59 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett,
Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn,
Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor,
Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa,
Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long,
kasan-dev, linux-crypto, linux-doc, linux-kbuild, linux-kernel,
linux-mm, linux-security-module, linux-sparse, llvm, rcu
In-Reply-To: <20250918140451.1289454-1-elver@google.com>
Clang's capability analysis can be made aware of functions that assert
that capabilities/locks are held.
Presence of these annotations causes the analysis to assume the
capability is held after calls to the annotated function, and avoid
false positives with complex control-flow; for example, where not all
control-flow paths in a function require a held lock, and therefore
marking the function with __must_hold(..) is inappropriate.
Signed-off-by: Marco Elver <elver@google.com>
---
v3:
* __assert -> __assume rename
---
include/linux/lockdep.h | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h
index 67964dc4db95..11b3d22555ff 100644
--- a/include/linux/lockdep.h
+++ b/include/linux/lockdep.h
@@ -282,16 +282,16 @@ extern void lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie);
do { WARN_ON_ONCE(debug_locks && !(cond)); } while (0)
#define lockdep_assert_held(l) \
- lockdep_assert(lockdep_is_held(l) != LOCK_STATE_NOT_HELD)
+ do { lockdep_assert(lockdep_is_held(l) != LOCK_STATE_NOT_HELD); __assume_cap(l); } while (0)
#define lockdep_assert_not_held(l) \
lockdep_assert(lockdep_is_held(l) != LOCK_STATE_HELD)
#define lockdep_assert_held_write(l) \
- lockdep_assert(lockdep_is_held_type(l, 0))
+ do { lockdep_assert(lockdep_is_held_type(l, 0)); __assume_cap(l); } while (0)
#define lockdep_assert_held_read(l) \
- lockdep_assert(lockdep_is_held_type(l, 1))
+ do { lockdep_assert(lockdep_is_held_type(l, 1)); __assume_shared_cap(l); } while (0)
#define lockdep_assert_held_once(l) \
lockdep_assert_once(lockdep_is_held(l) != LOCK_STATE_NOT_HELD)
@@ -389,10 +389,10 @@ extern int lockdep_is_held(const void *);
#define lockdep_assert(c) do { } while (0)
#define lockdep_assert_once(c) do { } while (0)
-#define lockdep_assert_held(l) do { (void)(l); } while (0)
+#define lockdep_assert_held(l) __assume_cap(l)
#define lockdep_assert_not_held(l) do { (void)(l); } while (0)
-#define lockdep_assert_held_write(l) do { (void)(l); } while (0)
-#define lockdep_assert_held_read(l) do { (void)(l); } while (0)
+#define lockdep_assert_held_write(l) __assume_cap(l)
+#define lockdep_assert_held_read(l) __assume_shared_cap(l)
#define lockdep_assert_held_once(l) do { (void)(l); } while (0)
#define lockdep_assert_none_held_once() do { } while (0)
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply related
* [PATCH v3 08/35] locking/rwlock, spinlock: Support Clang's capability analysis
From: Marco Elver @ 2025-09-18 13:59 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett,
Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn,
Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor,
Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa,
Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long,
kasan-dev, linux-crypto, linux-doc, linux-kbuild, linux-kernel,
linux-mm, linux-security-module, linux-sparse, llvm, rcu
In-Reply-To: <20250918140451.1289454-1-elver@google.com>
Add support for Clang's capability analysis for raw_spinlock_t,
spinlock_t, and rwlock. This wholesale conversion is required because
all three of them are interdependent.
To avoid warnings in constructors, the initialization functions mark a
capability as acquired when initialized before guarded variables.
The test verifies that common patterns do not generate false positives.
Signed-off-by: Marco Elver <elver@google.com>
---
v3:
* Switch to DECLARE_LOCK_GUARD_1_ATTRS() (suggested by Peter)
* __assert -> __assume rename
---
.../dev-tools/capability-analysis.rst | 3 +-
include/linux/rwlock.h | 25 ++--
include/linux/rwlock_api_smp.h | 29 +++-
include/linux/rwlock_rt.h | 35 +++--
include/linux/rwlock_types.h | 10 +-
include/linux/spinlock.h | 70 +++++++---
include/linux/spinlock_api_smp.h | 14 +-
include/linux/spinlock_api_up.h | 71 +++++-----
include/linux/spinlock_rt.h | 21 +--
include/linux/spinlock_types.h | 10 +-
include/linux/spinlock_types_raw.h | 5 +-
lib/test_capability-analysis.c | 128 ++++++++++++++++++
12 files changed, 324 insertions(+), 97 deletions(-)
diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 1287f792f6cd..9abd7f62cf4e 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -80,7 +80,8 @@ More details are also documented `here
Supported Kernel Primitives
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. Currently the following synchronization primitives are supported:
+Currently the following synchronization primitives are supported:
+`raw_spinlock_t`, `spinlock_t`, `rwlock_t`.
For capabilities with an initialization function (e.g., `spin_lock_init()`),
calling this function on the capability instance before initializing any
diff --git a/include/linux/rwlock.h b/include/linux/rwlock.h
index 5b87c6f4a243..78e4d02ee2c6 100644
--- a/include/linux/rwlock.h
+++ b/include/linux/rwlock.h
@@ -22,23 +22,24 @@ do { \
static struct lock_class_key __key; \
\
__rwlock_init((lock), #lock, &__key); \
+ __assume_cap(lock); \
} while (0)
#else
# define rwlock_init(lock) \
- do { *(lock) = __RW_LOCK_UNLOCKED(lock); } while (0)
+ do { *(lock) = __RW_LOCK_UNLOCKED(lock); __assume_cap(lock); } while (0)
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
- extern void do_raw_read_lock(rwlock_t *lock) __acquires(lock);
+ extern void do_raw_read_lock(rwlock_t *lock) __acquires_shared(lock);
extern int do_raw_read_trylock(rwlock_t *lock);
- extern void do_raw_read_unlock(rwlock_t *lock) __releases(lock);
+ extern void do_raw_read_unlock(rwlock_t *lock) __releases_shared(lock);
extern void do_raw_write_lock(rwlock_t *lock) __acquires(lock);
extern int do_raw_write_trylock(rwlock_t *lock);
extern void do_raw_write_unlock(rwlock_t *lock) __releases(lock);
#else
-# define do_raw_read_lock(rwlock) do {__acquire(lock); arch_read_lock(&(rwlock)->raw_lock); } while (0)
+# define do_raw_read_lock(rwlock) do {__acquire_shared(lock); arch_read_lock(&(rwlock)->raw_lock); } while (0)
# define do_raw_read_trylock(rwlock) arch_read_trylock(&(rwlock)->raw_lock)
-# define do_raw_read_unlock(rwlock) do {arch_read_unlock(&(rwlock)->raw_lock); __release(lock); } while (0)
+# define do_raw_read_unlock(rwlock) do {arch_read_unlock(&(rwlock)->raw_lock); __release_shared(lock); } while (0)
# define do_raw_write_lock(rwlock) do {__acquire(lock); arch_write_lock(&(rwlock)->raw_lock); } while (0)
# define do_raw_write_trylock(rwlock) arch_write_trylock(&(rwlock)->raw_lock)
# define do_raw_write_unlock(rwlock) do {arch_write_unlock(&(rwlock)->raw_lock); __release(lock); } while (0)
@@ -49,7 +50,7 @@ do { \
* regardless of whether CONFIG_SMP or CONFIG_PREEMPT are set. The various
* methods are defined as nops in the case they are not required.
*/
-#define read_trylock(lock) __cond_lock(lock, _raw_read_trylock(lock))
+#define read_trylock(lock) __cond_lock_shared(lock, _raw_read_trylock(lock))
#define write_trylock(lock) __cond_lock(lock, _raw_write_trylock(lock))
#define write_lock(lock) _raw_write_lock(lock)
@@ -112,12 +113,12 @@ do { \
} while (0)
#define write_unlock_bh(lock) _raw_write_unlock_bh(lock)
-#define write_trylock_irqsave(lock, flags) \
-({ \
- local_irq_save(flags); \
- write_trylock(lock) ? \
- 1 : ({ local_irq_restore(flags); 0; }); \
-})
+#define write_trylock_irqsave(lock, flags) \
+ __cond_lock(lock, ({ \
+ local_irq_save(flags); \
+ _raw_write_trylock(lock) ? \
+ 1 : ({ local_irq_restore(flags); 0; }); \
+ }))
#ifdef arch_rwlock_is_contended
#define rwlock_is_contended(lock) \
diff --git a/include/linux/rwlock_api_smp.h b/include/linux/rwlock_api_smp.h
index 31d3d1116323..3e975105a606 100644
--- a/include/linux/rwlock_api_smp.h
+++ b/include/linux/rwlock_api_smp.h
@@ -15,12 +15,12 @@
* Released under the General Public License (GPL).
*/
-void __lockfunc _raw_read_lock(rwlock_t *lock) __acquires(lock);
+void __lockfunc _raw_read_lock(rwlock_t *lock) __acquires_shared(lock);
void __lockfunc _raw_write_lock(rwlock_t *lock) __acquires(lock);
void __lockfunc _raw_write_lock_nested(rwlock_t *lock, int subclass) __acquires(lock);
-void __lockfunc _raw_read_lock_bh(rwlock_t *lock) __acquires(lock);
+void __lockfunc _raw_read_lock_bh(rwlock_t *lock) __acquires_shared(lock);
void __lockfunc _raw_write_lock_bh(rwlock_t *lock) __acquires(lock);
-void __lockfunc _raw_read_lock_irq(rwlock_t *lock) __acquires(lock);
+void __lockfunc _raw_read_lock_irq(rwlock_t *lock) __acquires_shared(lock);
void __lockfunc _raw_write_lock_irq(rwlock_t *lock) __acquires(lock);
unsigned long __lockfunc _raw_read_lock_irqsave(rwlock_t *lock)
__acquires(lock);
@@ -28,11 +28,11 @@ unsigned long __lockfunc _raw_write_lock_irqsave(rwlock_t *lock)
__acquires(lock);
int __lockfunc _raw_read_trylock(rwlock_t *lock);
int __lockfunc _raw_write_trylock(rwlock_t *lock);
-void __lockfunc _raw_read_unlock(rwlock_t *lock) __releases(lock);
+void __lockfunc _raw_read_unlock(rwlock_t *lock) __releases_shared(lock);
void __lockfunc _raw_write_unlock(rwlock_t *lock) __releases(lock);
-void __lockfunc _raw_read_unlock_bh(rwlock_t *lock) __releases(lock);
+void __lockfunc _raw_read_unlock_bh(rwlock_t *lock) __releases_shared(lock);
void __lockfunc _raw_write_unlock_bh(rwlock_t *lock) __releases(lock);
-void __lockfunc _raw_read_unlock_irq(rwlock_t *lock) __releases(lock);
+void __lockfunc _raw_read_unlock_irq(rwlock_t *lock) __releases_shared(lock);
void __lockfunc _raw_write_unlock_irq(rwlock_t *lock) __releases(lock);
void __lockfunc
_raw_read_unlock_irqrestore(rwlock_t *lock, unsigned long flags)
@@ -145,6 +145,7 @@ static inline int __raw_write_trylock(rwlock_t *lock)
#if !defined(CONFIG_GENERIC_LOCKBREAK) || defined(CONFIG_DEBUG_LOCK_ALLOC)
static inline void __raw_read_lock(rwlock_t *lock)
+ __acquires_shared(lock) __no_capability_analysis
{
preempt_disable();
rwlock_acquire_read(&lock->dep_map, 0, 0, _RET_IP_);
@@ -152,6 +153,7 @@ static inline void __raw_read_lock(rwlock_t *lock)
}
static inline unsigned long __raw_read_lock_irqsave(rwlock_t *lock)
+ __acquires_shared(lock) __no_capability_analysis
{
unsigned long flags;
@@ -163,6 +165,7 @@ static inline unsigned long __raw_read_lock_irqsave(rwlock_t *lock)
}
static inline void __raw_read_lock_irq(rwlock_t *lock)
+ __acquires_shared(lock) __no_capability_analysis
{
local_irq_disable();
preempt_disable();
@@ -171,6 +174,7 @@ static inline void __raw_read_lock_irq(rwlock_t *lock)
}
static inline void __raw_read_lock_bh(rwlock_t *lock)
+ __acquires_shared(lock) __no_capability_analysis
{
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);
rwlock_acquire_read(&lock->dep_map, 0, 0, _RET_IP_);
@@ -178,6 +182,7 @@ static inline void __raw_read_lock_bh(rwlock_t *lock)
}
static inline unsigned long __raw_write_lock_irqsave(rwlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
unsigned long flags;
@@ -189,6 +194,7 @@ static inline unsigned long __raw_write_lock_irqsave(rwlock_t *lock)
}
static inline void __raw_write_lock_irq(rwlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
local_irq_disable();
preempt_disable();
@@ -197,6 +203,7 @@ static inline void __raw_write_lock_irq(rwlock_t *lock)
}
static inline void __raw_write_lock_bh(rwlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);
rwlock_acquire(&lock->dep_map, 0, 0, _RET_IP_);
@@ -204,6 +211,7 @@ static inline void __raw_write_lock_bh(rwlock_t *lock)
}
static inline void __raw_write_lock(rwlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
preempt_disable();
rwlock_acquire(&lock->dep_map, 0, 0, _RET_IP_);
@@ -211,6 +219,7 @@ static inline void __raw_write_lock(rwlock_t *lock)
}
static inline void __raw_write_lock_nested(rwlock_t *lock, int subclass)
+ __acquires(lock) __no_capability_analysis
{
preempt_disable();
rwlock_acquire(&lock->dep_map, subclass, 0, _RET_IP_);
@@ -220,6 +229,7 @@ static inline void __raw_write_lock_nested(rwlock_t *lock, int subclass)
#endif /* !CONFIG_GENERIC_LOCKBREAK || CONFIG_DEBUG_LOCK_ALLOC */
static inline void __raw_write_unlock(rwlock_t *lock)
+ __releases(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_write_unlock(lock);
@@ -227,6 +237,7 @@ static inline void __raw_write_unlock(rwlock_t *lock)
}
static inline void __raw_read_unlock(rwlock_t *lock)
+ __releases_shared(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_read_unlock(lock);
@@ -235,6 +246,7 @@ static inline void __raw_read_unlock(rwlock_t *lock)
static inline void
__raw_read_unlock_irqrestore(rwlock_t *lock, unsigned long flags)
+ __releases_shared(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_read_unlock(lock);
@@ -243,6 +255,7 @@ __raw_read_unlock_irqrestore(rwlock_t *lock, unsigned long flags)
}
static inline void __raw_read_unlock_irq(rwlock_t *lock)
+ __releases_shared(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_read_unlock(lock);
@@ -251,6 +264,7 @@ static inline void __raw_read_unlock_irq(rwlock_t *lock)
}
static inline void __raw_read_unlock_bh(rwlock_t *lock)
+ __releases_shared(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_read_unlock(lock);
@@ -259,6 +273,7 @@ static inline void __raw_read_unlock_bh(rwlock_t *lock)
static inline void __raw_write_unlock_irqrestore(rwlock_t *lock,
unsigned long flags)
+ __releases(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_write_unlock(lock);
@@ -267,6 +282,7 @@ static inline void __raw_write_unlock_irqrestore(rwlock_t *lock,
}
static inline void __raw_write_unlock_irq(rwlock_t *lock)
+ __releases(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_write_unlock(lock);
@@ -275,6 +291,7 @@ static inline void __raw_write_unlock_irq(rwlock_t *lock)
}
static inline void __raw_write_unlock_bh(rwlock_t *lock)
+ __releases(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_write_unlock(lock);
diff --git a/include/linux/rwlock_rt.h b/include/linux/rwlock_rt.h
index 7d81fc6918ee..52ef2dc63a96 100644
--- a/include/linux/rwlock_rt.h
+++ b/include/linux/rwlock_rt.h
@@ -22,28 +22,32 @@ do { \
\
init_rwbase_rt(&(rwl)->rwbase); \
__rt_rwlock_init(rwl, #rwl, &__key); \
+ __assume_cap(rwl); \
} while (0)
-extern void rt_read_lock(rwlock_t *rwlock) __acquires(rwlock);
+extern void rt_read_lock(rwlock_t *rwlock) __acquires_shared(rwlock);
extern int rt_read_trylock(rwlock_t *rwlock);
-extern void rt_read_unlock(rwlock_t *rwlock) __releases(rwlock);
+extern void rt_read_unlock(rwlock_t *rwlock) __releases_shared(rwlock);
extern void rt_write_lock(rwlock_t *rwlock) __acquires(rwlock);
extern void rt_write_lock_nested(rwlock_t *rwlock, int subclass) __acquires(rwlock);
extern int rt_write_trylock(rwlock_t *rwlock);
extern void rt_write_unlock(rwlock_t *rwlock) __releases(rwlock);
static __always_inline void read_lock(rwlock_t *rwlock)
+ __acquires_shared(rwlock)
{
rt_read_lock(rwlock);
}
static __always_inline void read_lock_bh(rwlock_t *rwlock)
+ __acquires_shared(rwlock)
{
local_bh_disable();
rt_read_lock(rwlock);
}
static __always_inline void read_lock_irq(rwlock_t *rwlock)
+ __acquires_shared(rwlock)
{
rt_read_lock(rwlock);
}
@@ -55,37 +59,43 @@ static __always_inline void read_lock_irq(rwlock_t *rwlock)
flags = 0; \
} while (0)
-#define read_trylock(lock) __cond_lock(lock, rt_read_trylock(lock))
+#define read_trylock(lock) __cond_lock_shared(lock, rt_read_trylock(lock))
static __always_inline void read_unlock(rwlock_t *rwlock)
+ __releases_shared(rwlock)
{
rt_read_unlock(rwlock);
}
static __always_inline void read_unlock_bh(rwlock_t *rwlock)
+ __releases_shared(rwlock)
{
rt_read_unlock(rwlock);
local_bh_enable();
}
static __always_inline void read_unlock_irq(rwlock_t *rwlock)
+ __releases_shared(rwlock)
{
rt_read_unlock(rwlock);
}
static __always_inline void read_unlock_irqrestore(rwlock_t *rwlock,
unsigned long flags)
+ __releases_shared(rwlock)
{
rt_read_unlock(rwlock);
}
static __always_inline void write_lock(rwlock_t *rwlock)
+ __acquires(rwlock)
{
rt_write_lock(rwlock);
}
#ifdef CONFIG_DEBUG_LOCK_ALLOC
static __always_inline void write_lock_nested(rwlock_t *rwlock, int subclass)
+ __acquires(rwlock)
{
rt_write_lock_nested(rwlock, subclass);
}
@@ -94,12 +104,14 @@ static __always_inline void write_lock_nested(rwlock_t *rwlock, int subclass)
#endif
static __always_inline void write_lock_bh(rwlock_t *rwlock)
+ __acquires(rwlock)
{
local_bh_disable();
rt_write_lock(rwlock);
}
static __always_inline void write_lock_irq(rwlock_t *rwlock)
+ __acquires(rwlock)
{
rt_write_lock(rwlock);
}
@@ -114,33 +126,34 @@ static __always_inline void write_lock_irq(rwlock_t *rwlock)
#define write_trylock(lock) __cond_lock(lock, rt_write_trylock(lock))
#define write_trylock_irqsave(lock, flags) \
-({ \
- int __locked; \
- \
- typecheck(unsigned long, flags); \
- flags = 0; \
- __locked = write_trylock(lock); \
- __locked; \
-})
+ __cond_lock(lock, ({ \
+ typecheck(unsigned long, flags); \
+ flags = 0; \
+ rt_write_trylock(lock); \
+ }))
static __always_inline void write_unlock(rwlock_t *rwlock)
+ __releases(rwlock)
{
rt_write_unlock(rwlock);
}
static __always_inline void write_unlock_bh(rwlock_t *rwlock)
+ __releases(rwlock)
{
rt_write_unlock(rwlock);
local_bh_enable();
}
static __always_inline void write_unlock_irq(rwlock_t *rwlock)
+ __releases(rwlock)
{
rt_write_unlock(rwlock);
}
static __always_inline void write_unlock_irqrestore(rwlock_t *rwlock,
unsigned long flags)
+ __releases(rwlock)
{
rt_write_unlock(rwlock);
}
diff --git a/include/linux/rwlock_types.h b/include/linux/rwlock_types.h
index 1948442e7750..231489cc30f2 100644
--- a/include/linux/rwlock_types.h
+++ b/include/linux/rwlock_types.h
@@ -22,7 +22,7 @@
* portions Copyright 2005, Red Hat, Inc., Ingo Molnar
* Released under the General Public License (GPL).
*/
-typedef struct {
+struct_with_capability(rwlock) {
arch_rwlock_t raw_lock;
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
@@ -31,7 +31,8 @@ typedef struct {
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
-} rwlock_t;
+};
+typedef struct rwlock rwlock_t;
#define RWLOCK_MAGIC 0xdeaf1eed
@@ -54,13 +55,14 @@ typedef struct {
#include <linux/rwbase_rt.h>
-typedef struct {
+struct_with_capability(rwlock) {
struct rwbase_rt rwbase;
atomic_t readers;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
-} rwlock_t;
+};
+typedef struct rwlock rwlock_t;
#define __RWLOCK_RT_INITIALIZER(name) \
{ \
diff --git a/include/linux/spinlock.h b/include/linux/spinlock.h
index d3561c4a080e..7679f39071e9 100644
--- a/include/linux/spinlock.h
+++ b/include/linux/spinlock.h
@@ -106,11 +106,12 @@ do { \
static struct lock_class_key __key; \
\
__raw_spin_lock_init((lock), #lock, &__key, LD_WAIT_SPIN); \
+ __assume_cap(lock); \
} while (0)
#else
# define raw_spin_lock_init(lock) \
- do { *(lock) = __RAW_SPIN_LOCK_UNLOCKED(lock); } while (0)
+ do { *(lock) = __RAW_SPIN_LOCK_UNLOCKED(lock); __assume_cap(lock); } while (0)
#endif
#define raw_spin_is_locked(lock) arch_spin_is_locked(&(lock)->raw_lock)
@@ -286,19 +287,19 @@ static inline void do_raw_spin_unlock(raw_spinlock_t *lock) __releases(lock)
#define raw_spin_trylock_bh(lock) \
__cond_lock(lock, _raw_spin_trylock_bh(lock))
-#define raw_spin_trylock_irq(lock) \
-({ \
- local_irq_disable(); \
- raw_spin_trylock(lock) ? \
- 1 : ({ local_irq_enable(); 0; }); \
-})
+#define raw_spin_trylock_irq(lock) \
+ __cond_lock(lock, ({ \
+ local_irq_disable(); \
+ _raw_spin_trylock(lock) ? \
+ 1 : ({ local_irq_enable(); 0; }); \
+ }))
-#define raw_spin_trylock_irqsave(lock, flags) \
-({ \
- local_irq_save(flags); \
- raw_spin_trylock(lock) ? \
- 1 : ({ local_irq_restore(flags); 0; }); \
-})
+#define raw_spin_trylock_irqsave(lock, flags) \
+ __cond_lock(lock, ({ \
+ local_irq_save(flags); \
+ _raw_spin_trylock(lock) ? \
+ 1 : ({ local_irq_restore(flags); 0; }); \
+ }))
#ifndef CONFIG_PREEMPT_RT
/* Include rwlock functions for !RT */
@@ -334,6 +335,7 @@ do { \
\
__raw_spin_lock_init(spinlock_check(lock), \
#lock, &__key, LD_WAIT_CONFIG); \
+ __assume_cap(lock); \
} while (0)
#else
@@ -342,21 +344,25 @@ do { \
do { \
spinlock_check(_lock); \
*(_lock) = __SPIN_LOCK_UNLOCKED(_lock); \
+ __assume_cap(_lock); \
} while (0)
#endif
static __always_inline void spin_lock(spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
raw_spin_lock(&lock->rlock);
}
static __always_inline void spin_lock_bh(spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
raw_spin_lock_bh(&lock->rlock);
}
static __always_inline int spin_trylock(spinlock_t *lock)
+ __cond_acquires(lock) __no_capability_analysis
{
return raw_spin_trylock(&lock->rlock);
}
@@ -364,14 +370,17 @@ static __always_inline int spin_trylock(spinlock_t *lock)
#define spin_lock_nested(lock, subclass) \
do { \
raw_spin_lock_nested(spinlock_check(lock), subclass); \
+ __release(spinlock_check(lock)); __acquire(lock); \
} while (0)
#define spin_lock_nest_lock(lock, nest_lock) \
do { \
raw_spin_lock_nest_lock(spinlock_check(lock), nest_lock); \
+ __release(spinlock_check(lock)); __acquire(lock); \
} while (0)
static __always_inline void spin_lock_irq(spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
raw_spin_lock_irq(&lock->rlock);
}
@@ -379,47 +388,53 @@ static __always_inline void spin_lock_irq(spinlock_t *lock)
#define spin_lock_irqsave(lock, flags) \
do { \
raw_spin_lock_irqsave(spinlock_check(lock), flags); \
+ __release(spinlock_check(lock)); __acquire(lock); \
} while (0)
#define spin_lock_irqsave_nested(lock, flags, subclass) \
do { \
raw_spin_lock_irqsave_nested(spinlock_check(lock), flags, subclass); \
+ __release(spinlock_check(lock)); __acquire(lock); \
} while (0)
static __always_inline void spin_unlock(spinlock_t *lock)
+ __releases(lock) __no_capability_analysis
{
raw_spin_unlock(&lock->rlock);
}
static __always_inline void spin_unlock_bh(spinlock_t *lock)
+ __releases(lock) __no_capability_analysis
{
raw_spin_unlock_bh(&lock->rlock);
}
static __always_inline void spin_unlock_irq(spinlock_t *lock)
+ __releases(lock) __no_capability_analysis
{
raw_spin_unlock_irq(&lock->rlock);
}
static __always_inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
+ __releases(lock) __no_capability_analysis
{
raw_spin_unlock_irqrestore(&lock->rlock, flags);
}
static __always_inline int spin_trylock_bh(spinlock_t *lock)
+ __cond_acquires(lock) __no_capability_analysis
{
return raw_spin_trylock_bh(&lock->rlock);
}
static __always_inline int spin_trylock_irq(spinlock_t *lock)
+ __cond_acquires(lock) __no_capability_analysis
{
return raw_spin_trylock_irq(&lock->rlock);
}
#define spin_trylock_irqsave(lock, flags) \
-({ \
- raw_spin_trylock_irqsave(spinlock_check(lock), flags); \
-})
+ __cond_lock(lock, raw_spin_trylock_irqsave(spinlock_check(lock), flags))
/**
* spin_is_locked() - Check whether a spinlock is locked.
@@ -535,86 +550,109 @@ void free_bucket_spinlocks(spinlock_t *locks);
DEFINE_LOCK_GUARD_1(raw_spinlock, raw_spinlock_t,
raw_spin_lock(_T->lock),
raw_spin_unlock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1_COND(raw_spinlock, _try, raw_spin_trylock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_try, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(raw_spinlock_nested, raw_spinlock_t,
raw_spin_lock_nested(_T->lock, SINGLE_DEPTH_NESTING),
raw_spin_unlock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_nested, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(raw_spinlock_irq, raw_spinlock_t,
raw_spin_lock_irq(_T->lock),
raw_spin_unlock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irq, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1_COND(raw_spinlock_irq, _try, raw_spin_trylock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irq_try, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(raw_spinlock_bh, raw_spinlock_t,
raw_spin_lock_bh(_T->lock),
raw_spin_unlock_bh(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_bh, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1_COND(raw_spinlock_bh, _try, raw_spin_trylock_bh(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_bh_try, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(raw_spinlock_irqsave, raw_spinlock_t,
raw_spin_lock_irqsave(_T->lock, _T->flags),
raw_spin_unlock_irqrestore(_T->lock, _T->flags),
unsigned long flags)
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irqsave, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1_COND(raw_spinlock_irqsave, _try,
raw_spin_trylock_irqsave(_T->lock, _T->flags))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irqsave_try, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(spinlock, spinlock_t,
spin_lock(_T->lock),
spin_unlock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1_COND(spinlock, _try, spin_trylock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_try, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(spinlock_irq, spinlock_t,
spin_lock_irq(_T->lock),
spin_unlock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irq, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1_COND(spinlock_irq, _try,
spin_trylock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irq_try, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(spinlock_bh, spinlock_t,
spin_lock_bh(_T->lock),
spin_unlock_bh(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_bh, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1_COND(spinlock_bh, _try,
spin_trylock_bh(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_bh_try, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(spinlock_irqsave, spinlock_t,
spin_lock_irqsave(_T->lock, _T->flags),
spin_unlock_irqrestore(_T->lock, _T->flags),
unsigned long flags)
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irqsave, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1_COND(spinlock_irqsave, _try,
spin_trylock_irqsave(_T->lock, _T->flags))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irqsave_try, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(read_lock, rwlock_t,
read_lock(_T->lock),
read_unlock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(read_lock, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(read_lock_irq, rwlock_t,
read_lock_irq(_T->lock),
read_unlock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(read_lock_irq, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(read_lock_irqsave, rwlock_t,
read_lock_irqsave(_T->lock, _T->flags),
read_unlock_irqrestore(_T->lock, _T->flags),
unsigned long flags)
+DECLARE_LOCK_GUARD_1_ATTRS(read_lock_irqsave, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(write_lock, rwlock_t,
write_lock(_T->lock),
write_unlock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(write_lock, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(write_lock_irq, rwlock_t,
write_lock_irq(_T->lock),
write_unlock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(write_lock_irq, __assumes_cap(_T), /* */)
DEFINE_LOCK_GUARD_1(write_lock_irqsave, rwlock_t,
write_lock_irqsave(_T->lock, _T->flags),
write_unlock_irqrestore(_T->lock, _T->flags),
unsigned long flags)
+DECLARE_LOCK_GUARD_1_ATTRS(write_lock_irqsave, __assumes_cap(_T), /* */)
#undef __LINUX_INSIDE_SPINLOCK_H
#endif /* __LINUX_SPINLOCK_H */
diff --git a/include/linux/spinlock_api_smp.h b/include/linux/spinlock_api_smp.h
index 9ecb0ab504e3..fab02d8bf0c9 100644
--- a/include/linux/spinlock_api_smp.h
+++ b/include/linux/spinlock_api_smp.h
@@ -34,8 +34,8 @@ unsigned long __lockfunc _raw_spin_lock_irqsave(raw_spinlock_t *lock)
unsigned long __lockfunc
_raw_spin_lock_irqsave_nested(raw_spinlock_t *lock, int subclass)
__acquires(lock);
-int __lockfunc _raw_spin_trylock(raw_spinlock_t *lock);
-int __lockfunc _raw_spin_trylock_bh(raw_spinlock_t *lock);
+int __lockfunc _raw_spin_trylock(raw_spinlock_t *lock) __cond_acquires(lock);
+int __lockfunc _raw_spin_trylock_bh(raw_spinlock_t *lock) __cond_acquires(lock);
void __lockfunc _raw_spin_unlock(raw_spinlock_t *lock) __releases(lock);
void __lockfunc _raw_spin_unlock_bh(raw_spinlock_t *lock) __releases(lock);
void __lockfunc _raw_spin_unlock_irq(raw_spinlock_t *lock) __releases(lock);
@@ -84,6 +84,7 @@ _raw_spin_unlock_irqrestore(raw_spinlock_t *lock, unsigned long flags)
#endif
static inline int __raw_spin_trylock(raw_spinlock_t *lock)
+ __cond_acquires(lock)
{
preempt_disable();
if (do_raw_spin_trylock(lock)) {
@@ -102,6 +103,7 @@ static inline int __raw_spin_trylock(raw_spinlock_t *lock)
#if !defined(CONFIG_GENERIC_LOCKBREAK) || defined(CONFIG_DEBUG_LOCK_ALLOC)
static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
unsigned long flags;
@@ -113,6 +115,7 @@ static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
}
static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
local_irq_disable();
preempt_disable();
@@ -121,6 +124,7 @@ static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
}
static inline void __raw_spin_lock_bh(raw_spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
@@ -128,6 +132,7 @@ static inline void __raw_spin_lock_bh(raw_spinlock_t *lock)
}
static inline void __raw_spin_lock(raw_spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
@@ -137,6 +142,7 @@ static inline void __raw_spin_lock(raw_spinlock_t *lock)
#endif /* !CONFIG_GENERIC_LOCKBREAK || CONFIG_DEBUG_LOCK_ALLOC */
static inline void __raw_spin_unlock(raw_spinlock_t *lock)
+ __releases(lock)
{
spin_release(&lock->dep_map, _RET_IP_);
do_raw_spin_unlock(lock);
@@ -145,6 +151,7 @@ static inline void __raw_spin_unlock(raw_spinlock_t *lock)
static inline void __raw_spin_unlock_irqrestore(raw_spinlock_t *lock,
unsigned long flags)
+ __releases(lock)
{
spin_release(&lock->dep_map, _RET_IP_);
do_raw_spin_unlock(lock);
@@ -153,6 +160,7 @@ static inline void __raw_spin_unlock_irqrestore(raw_spinlock_t *lock,
}
static inline void __raw_spin_unlock_irq(raw_spinlock_t *lock)
+ __releases(lock)
{
spin_release(&lock->dep_map, _RET_IP_);
do_raw_spin_unlock(lock);
@@ -161,6 +169,7 @@ static inline void __raw_spin_unlock_irq(raw_spinlock_t *lock)
}
static inline void __raw_spin_unlock_bh(raw_spinlock_t *lock)
+ __releases(lock)
{
spin_release(&lock->dep_map, _RET_IP_);
do_raw_spin_unlock(lock);
@@ -168,6 +177,7 @@ static inline void __raw_spin_unlock_bh(raw_spinlock_t *lock)
}
static inline int __raw_spin_trylock_bh(raw_spinlock_t *lock)
+ __cond_acquires(lock)
{
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);
if (do_raw_spin_trylock(lock)) {
diff --git a/include/linux/spinlock_api_up.h b/include/linux/spinlock_api_up.h
index 819aeba1c87e..018f5aabc1be 100644
--- a/include/linux/spinlock_api_up.h
+++ b/include/linux/spinlock_api_up.h
@@ -24,68 +24,77 @@
* flags straight, to suppress compiler warnings of unused lock
* variables, and to add the proper checker annotations:
*/
-#define ___LOCK(lock) \
- do { __acquire(lock); (void)(lock); } while (0)
+#define ___LOCK_void(lock) \
+ do { (void)(lock); } while (0)
-#define __LOCK(lock) \
- do { preempt_disable(); ___LOCK(lock); } while (0)
+#define ___LOCK_(lock) \
+ do { __acquire(lock); ___LOCK_void(lock); } while (0)
-#define __LOCK_BH(lock) \
- do { __local_bh_disable_ip(_THIS_IP_, SOFTIRQ_LOCK_OFFSET); ___LOCK(lock); } while (0)
+#define ___LOCK_shared(lock) \
+ do { __acquire_shared(lock); ___LOCK_void(lock); } while (0)
-#define __LOCK_IRQ(lock) \
- do { local_irq_disable(); __LOCK(lock); } while (0)
+#define __LOCK(lock, ...) \
+ do { preempt_disable(); ___LOCK_##__VA_ARGS__(lock); } while (0)
-#define __LOCK_IRQSAVE(lock, flags) \
- do { local_irq_save(flags); __LOCK(lock); } while (0)
+#define __LOCK_BH(lock, ...) \
+ do { __local_bh_disable_ip(_THIS_IP_, SOFTIRQ_LOCK_OFFSET); ___LOCK_##__VA_ARGS__(lock); } while (0)
-#define ___UNLOCK(lock) \
+#define __LOCK_IRQ(lock, ...) \
+ do { local_irq_disable(); __LOCK(lock, ##__VA_ARGS__); } while (0)
+
+#define __LOCK_IRQSAVE(lock, flags, ...) \
+ do { local_irq_save(flags); __LOCK(lock, ##__VA_ARGS__); } while (0)
+
+#define ___UNLOCK_(lock) \
do { __release(lock); (void)(lock); } while (0)
-#define __UNLOCK(lock) \
- do { preempt_enable(); ___UNLOCK(lock); } while (0)
+#define ___UNLOCK_shared(lock) \
+ do { __release_shared(lock); (void)(lock); } while (0)
-#define __UNLOCK_BH(lock) \
+#define __UNLOCK(lock, ...) \
+ do { preempt_enable(); ___UNLOCK_##__VA_ARGS__(lock); } while (0)
+
+#define __UNLOCK_BH(lock, ...) \
do { __local_bh_enable_ip(_THIS_IP_, SOFTIRQ_LOCK_OFFSET); \
- ___UNLOCK(lock); } while (0)
+ ___UNLOCK_##__VA_ARGS__(lock); } while (0)
-#define __UNLOCK_IRQ(lock) \
- do { local_irq_enable(); __UNLOCK(lock); } while (0)
+#define __UNLOCK_IRQ(lock, ...) \
+ do { local_irq_enable(); __UNLOCK(lock, ##__VA_ARGS__); } while (0)
-#define __UNLOCK_IRQRESTORE(lock, flags) \
- do { local_irq_restore(flags); __UNLOCK(lock); } while (0)
+#define __UNLOCK_IRQRESTORE(lock, flags, ...) \
+ do { local_irq_restore(flags); __UNLOCK(lock, ##__VA_ARGS__); } while (0)
#define _raw_spin_lock(lock) __LOCK(lock)
#define _raw_spin_lock_nested(lock, subclass) __LOCK(lock)
-#define _raw_read_lock(lock) __LOCK(lock)
+#define _raw_read_lock(lock) __LOCK(lock, shared)
#define _raw_write_lock(lock) __LOCK(lock)
#define _raw_write_lock_nested(lock, subclass) __LOCK(lock)
#define _raw_spin_lock_bh(lock) __LOCK_BH(lock)
-#define _raw_read_lock_bh(lock) __LOCK_BH(lock)
+#define _raw_read_lock_bh(lock) __LOCK_BH(lock, shared)
#define _raw_write_lock_bh(lock) __LOCK_BH(lock)
#define _raw_spin_lock_irq(lock) __LOCK_IRQ(lock)
-#define _raw_read_lock_irq(lock) __LOCK_IRQ(lock)
+#define _raw_read_lock_irq(lock) __LOCK_IRQ(lock, shared)
#define _raw_write_lock_irq(lock) __LOCK_IRQ(lock)
#define _raw_spin_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
-#define _raw_read_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
+#define _raw_read_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags, shared)
#define _raw_write_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
-#define _raw_spin_trylock(lock) ({ __LOCK(lock); 1; })
-#define _raw_read_trylock(lock) ({ __LOCK(lock); 1; })
-#define _raw_write_trylock(lock) ({ __LOCK(lock); 1; })
-#define _raw_spin_trylock_bh(lock) ({ __LOCK_BH(lock); 1; })
+#define _raw_spin_trylock(lock) ({ __LOCK(lock, void); 1; })
+#define _raw_read_trylock(lock) ({ __LOCK(lock, void); 1; })
+#define _raw_write_trylock(lock) ({ __LOCK(lock, void); 1; })
+#define _raw_spin_trylock_bh(lock) ({ __LOCK_BH(lock, void); 1; })
#define _raw_spin_unlock(lock) __UNLOCK(lock)
-#define _raw_read_unlock(lock) __UNLOCK(lock)
+#define _raw_read_unlock(lock) __UNLOCK(lock, shared)
#define _raw_write_unlock(lock) __UNLOCK(lock)
#define _raw_spin_unlock_bh(lock) __UNLOCK_BH(lock)
#define _raw_write_unlock_bh(lock) __UNLOCK_BH(lock)
-#define _raw_read_unlock_bh(lock) __UNLOCK_BH(lock)
+#define _raw_read_unlock_bh(lock) __UNLOCK_BH(lock, shared)
#define _raw_spin_unlock_irq(lock) __UNLOCK_IRQ(lock)
-#define _raw_read_unlock_irq(lock) __UNLOCK_IRQ(lock)
+#define _raw_read_unlock_irq(lock) __UNLOCK_IRQ(lock, shared)
#define _raw_write_unlock_irq(lock) __UNLOCK_IRQ(lock)
#define _raw_spin_unlock_irqrestore(lock, flags) \
__UNLOCK_IRQRESTORE(lock, flags)
#define _raw_read_unlock_irqrestore(lock, flags) \
- __UNLOCK_IRQRESTORE(lock, flags)
+ __UNLOCK_IRQRESTORE(lock, flags, shared)
#define _raw_write_unlock_irqrestore(lock, flags) \
__UNLOCK_IRQRESTORE(lock, flags)
diff --git a/include/linux/spinlock_rt.h b/include/linux/spinlock_rt.h
index f6499c37157d..9688675b7536 100644
--- a/include/linux/spinlock_rt.h
+++ b/include/linux/spinlock_rt.h
@@ -20,6 +20,7 @@ static inline void __rt_spin_lock_init(spinlock_t *lock, const char *name,
do { \
rt_mutex_base_init(&(slock)->lock); \
__rt_spin_lock_init(slock, name, key, percpu); \
+ __assume_cap(slock); \
} while (0)
#define _spin_lock_init(slock, percpu) \
@@ -40,6 +41,7 @@ extern int rt_spin_trylock_bh(spinlock_t *lock);
extern int rt_spin_trylock(spinlock_t *lock);
static __always_inline void spin_lock(spinlock_t *lock)
+ __acquires(lock)
{
rt_spin_lock(lock);
}
@@ -82,6 +84,7 @@ static __always_inline void spin_lock(spinlock_t *lock)
__spin_lock_irqsave_nested(lock, flags, subclass)
static __always_inline void spin_lock_bh(spinlock_t *lock)
+ __acquires(lock)
{
/* Investigate: Drop bh when blocking ? */
local_bh_disable();
@@ -89,6 +92,7 @@ static __always_inline void spin_lock_bh(spinlock_t *lock)
}
static __always_inline void spin_lock_irq(spinlock_t *lock)
+ __acquires(lock)
{
rt_spin_lock(lock);
}
@@ -101,23 +105,27 @@ static __always_inline void spin_lock_irq(spinlock_t *lock)
} while (0)
static __always_inline void spin_unlock(spinlock_t *lock)
+ __releases(lock)
{
rt_spin_unlock(lock);
}
static __always_inline void spin_unlock_bh(spinlock_t *lock)
+ __releases(lock)
{
rt_spin_unlock(lock);
local_bh_enable();
}
static __always_inline void spin_unlock_irq(spinlock_t *lock)
+ __releases(lock)
{
rt_spin_unlock(lock);
}
static __always_inline void spin_unlock_irqrestore(spinlock_t *lock,
unsigned long flags)
+ __releases(lock)
{
rt_spin_unlock(lock);
}
@@ -132,14 +140,11 @@ static __always_inline void spin_unlock_irqrestore(spinlock_t *lock,
__cond_lock(lock, rt_spin_trylock(lock))
#define spin_trylock_irqsave(lock, flags) \
-({ \
- int __locked; \
- \
- typecheck(unsigned long, flags); \
- flags = 0; \
- __locked = spin_trylock(lock); \
- __locked; \
-})
+ __cond_lock(lock, ({ \
+ typecheck(unsigned long, flags); \
+ flags = 0; \
+ rt_spin_trylock(lock); \
+ }))
#define spin_is_contended(lock) (((void)(lock), 0))
diff --git a/include/linux/spinlock_types.h b/include/linux/spinlock_types.h
index 2dfa35ffec76..2c5db5b5b990 100644
--- a/include/linux/spinlock_types.h
+++ b/include/linux/spinlock_types.h
@@ -14,7 +14,7 @@
#ifndef CONFIG_PREEMPT_RT
/* Non PREEMPT_RT kernels map spinlock to raw_spinlock */
-typedef struct spinlock {
+struct_with_capability(spinlock) {
union {
struct raw_spinlock rlock;
@@ -26,7 +26,8 @@ typedef struct spinlock {
};
#endif
};
-} spinlock_t;
+};
+typedef struct spinlock spinlock_t;
#define ___SPIN_LOCK_INITIALIZER(lockname) \
{ \
@@ -47,12 +48,13 @@ typedef struct spinlock {
/* PREEMPT_RT kernels map spinlock to rt_mutex */
#include <linux/rtmutex.h>
-typedef struct spinlock {
+struct_with_capability(spinlock) {
struct rt_mutex_base lock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
-} spinlock_t;
+};
+typedef struct spinlock spinlock_t;
#define __SPIN_LOCK_UNLOCKED(name) \
{ \
diff --git a/include/linux/spinlock_types_raw.h b/include/linux/spinlock_types_raw.h
index 91cb36b65a17..07792ff2c2b5 100644
--- a/include/linux/spinlock_types_raw.h
+++ b/include/linux/spinlock_types_raw.h
@@ -11,7 +11,7 @@
#include <linux/lockdep_types.h>
-typedef struct raw_spinlock {
+struct_with_capability(raw_spinlock) {
arch_spinlock_t raw_lock;
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
@@ -20,7 +20,8 @@ typedef struct raw_spinlock {
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
-} raw_spinlock_t;
+};
+typedef struct raw_spinlock raw_spinlock_t;
#define SPINLOCK_MAGIC 0xdead4ead
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index a0adacce30ff..84060bace61d 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -5,6 +5,7 @@
*/
#include <linux/build_bug.h>
+#include <linux/spinlock.h>
/*
* Test that helper macros work as expected.
@@ -16,3 +17,130 @@ static void __used test_common_helpers(void)
BUILD_BUG_ON(capability_unsafe((void)2, 3) != 3); /* does not swallow commas */
capability_unsafe(do { } while (0)); /* works with void statements */
}
+
+#define TEST_SPINLOCK_COMMON(class, type, type_init, type_lock, type_unlock, type_trylock, op) \
+ struct test_##class##_data { \
+ type lock; \
+ int counter __guarded_by(&lock); \
+ int *pointer __pt_guarded_by(&lock); \
+ }; \
+ static void __used test_##class##_init(struct test_##class##_data *d) \
+ { \
+ type_init(&d->lock); \
+ d->counter = 0; \
+ } \
+ static void __used test_##class(struct test_##class##_data *d) \
+ { \
+ unsigned long flags; \
+ d->pointer++; \
+ type_lock(&d->lock); \
+ op(d->counter); \
+ op(*d->pointer); \
+ type_unlock(&d->lock); \
+ type_lock##_irq(&d->lock); \
+ op(d->counter); \
+ op(*d->pointer); \
+ type_unlock##_irq(&d->lock); \
+ type_lock##_bh(&d->lock); \
+ op(d->counter); \
+ op(*d->pointer); \
+ type_unlock##_bh(&d->lock); \
+ type_lock##_irqsave(&d->lock, flags); \
+ op(d->counter); \
+ op(*d->pointer); \
+ type_unlock##_irqrestore(&d->lock, flags); \
+ } \
+ static void __used test_##class##_trylock(struct test_##class##_data *d) \
+ { \
+ if (type_trylock(&d->lock)) { \
+ op(d->counter); \
+ type_unlock(&d->lock); \
+ } \
+ } \
+ static void __used test_##class##_assert(struct test_##class##_data *d) \
+ { \
+ lockdep_assert_held(&d->lock); \
+ op(d->counter); \
+ } \
+ static void __used test_##class##_guard(struct test_##class##_data *d) \
+ { \
+ { guard(class)(&d->lock); op(d->counter); } \
+ { guard(class##_irq)(&d->lock); op(d->counter); } \
+ { guard(class##_irqsave)(&d->lock); op(d->counter); } \
+ }
+
+#define TEST_OP_RW(x) (x)++
+#define TEST_OP_RO(x) ((void)(x))
+
+TEST_SPINLOCK_COMMON(raw_spinlock,
+ raw_spinlock_t,
+ raw_spin_lock_init,
+ raw_spin_lock,
+ raw_spin_unlock,
+ raw_spin_trylock,
+ TEST_OP_RW);
+static void __used test_raw_spinlock_trylock_extra(struct test_raw_spinlock_data *d)
+{
+ unsigned long flags;
+
+ if (raw_spin_trylock_irq(&d->lock)) {
+ d->counter++;
+ raw_spin_unlock_irq(&d->lock);
+ }
+ if (raw_spin_trylock_irqsave(&d->lock, flags)) {
+ d->counter++;
+ raw_spin_unlock_irqrestore(&d->lock, flags);
+ }
+ scoped_cond_guard(raw_spinlock_try, return, &d->lock) {
+ d->counter++;
+ }
+}
+
+TEST_SPINLOCK_COMMON(spinlock,
+ spinlock_t,
+ spin_lock_init,
+ spin_lock,
+ spin_unlock,
+ spin_trylock,
+ TEST_OP_RW);
+static void __used test_spinlock_trylock_extra(struct test_spinlock_data *d)
+{
+ unsigned long flags;
+
+ if (spin_trylock_irq(&d->lock)) {
+ d->counter++;
+ spin_unlock_irq(&d->lock);
+ }
+ if (spin_trylock_irqsave(&d->lock, flags)) {
+ d->counter++;
+ spin_unlock_irqrestore(&d->lock, flags);
+ }
+ scoped_cond_guard(spinlock_try, return, &d->lock) {
+ d->counter++;
+ }
+}
+
+TEST_SPINLOCK_COMMON(write_lock,
+ rwlock_t,
+ rwlock_init,
+ write_lock,
+ write_unlock,
+ write_trylock,
+ TEST_OP_RW);
+static void __used test_write_trylock_extra(struct test_write_lock_data *d)
+{
+ unsigned long flags;
+
+ if (write_trylock_irqsave(&d->lock, flags)) {
+ d->counter++;
+ write_unlock_irqrestore(&d->lock, flags);
+ }
+}
+
+TEST_SPINLOCK_COMMON(read_lock,
+ rwlock_t,
+ rwlock_init,
+ read_lock,
+ read_unlock,
+ read_trylock,
+ TEST_OP_RO);
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply related
* [PATCH v3 09/35] compiler-capability-analysis: Change __cond_acquires to take return value
From: Marco Elver @ 2025-09-18 13:59 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett,
Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn,
Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor,
Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa,
Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long,
kasan-dev, linux-crypto, linux-doc, linux-kbuild, linux-kernel,
linux-mm, linux-security-module, linux-sparse, llvm, rcu
In-Reply-To: <20250918140451.1289454-1-elver@google.com>
While Sparse is oblivious to the return value of conditional acquire
functions, Clang's capability analysis needs to know the return value
which indicates successful acquisition.
Add the additional argument, and convert existing uses.
Notably, Clang's interpretation of the value merely relates to the use
in a later conditional branch, i.e. 1 ==> capability acquired in branch
taken if condition non-zero, and 0 ==> capability acquired in branch
taken if condition is zero. Given the precise value does not matter,
introduce symbolic variants to use instead of either 0 or 1, which
should be more intuitive.
No functional change intended.
Signed-off-by: Marco Elver <elver@google.com>
---
v2:
* Use symbolic values for __cond_acquires() and __cond_acquires_shared()
(suggested by Bart).
---
fs/dlm/lock.c | 2 +-
include/linux/compiler-capability-analysis.h | 31 ++++++++++++++++----
include/linux/refcount.h | 6 ++--
include/linux/spinlock.h | 6 ++--
include/linux/spinlock_api_smp.h | 8 ++---
net/ipv4/tcp_sigpool.c | 2 +-
6 files changed, 38 insertions(+), 17 deletions(-)
diff --git a/fs/dlm/lock.c b/fs/dlm/lock.c
index 6dd3a524cd35..006eb284c8a7 100644
--- a/fs/dlm/lock.c
+++ b/fs/dlm/lock.c
@@ -343,7 +343,7 @@ void dlm_hold_rsb(struct dlm_rsb *r)
/* TODO move this to lib/refcount.c */
static __must_check bool
dlm_refcount_dec_and_write_lock_bh(refcount_t *r, rwlock_t *lock)
-__cond_acquires(lock)
+ __cond_acquires(true, lock)
{
if (refcount_dec_not_one(r))
return false;
diff --git a/include/linux/compiler-capability-analysis.h b/include/linux/compiler-capability-analysis.h
index 6f3f185478bc..ccd312dbbf06 100644
--- a/include/linux/compiler-capability-analysis.h
+++ b/include/linux/compiler-capability-analysis.h
@@ -257,7 +257,7 @@ static inline void _capability_unsafe_alias(void **p) { }
# define __must_hold(x) __attribute__((context(x,1,1)))
# define __must_not_hold(x)
# define __acquires(x) __attribute__((context(x,0,1)))
-# define __cond_acquires(x) __attribute__((context(x,0,-1)))
+# define __cond_acquires(ret, x) __attribute__((context(x,0,-1)))
# define __releases(x) __attribute__((context(x,1,0)))
# define __acquire(x) __context__(x,1)
# define __release(x) __context__(x,-1)
@@ -300,15 +300,32 @@ static inline void _capability_unsafe_alias(void **p) { }
*/
# define __acquires(x) __acquires_cap(x)
+/*
+ * Clang's analysis does not care precisely about the value, only that it is
+ * either zero or non-zero. So the __cond_acquires() interface might be
+ * misleading if we say that @ret is the value returned if acquired. Instead,
+ * provide symbolic variants which we translate.
+ */
+#define __cond_acquires_impl_true(x, ...) __try_acquires##__VA_ARGS__##_cap(1, x)
+#define __cond_acquires_impl_false(x, ...) __try_acquires##__VA_ARGS__##_cap(0, x)
+#define __cond_acquires_impl_nonzero(x, ...) __try_acquires##__VA_ARGS__##_cap(1, x)
+#define __cond_acquires_impl_0(x, ...) __try_acquires##__VA_ARGS__##_cap(0, x)
+#define __cond_acquires_impl_nonnull(x, ...) __try_acquires##__VA_ARGS__##_cap(1, x)
+#define __cond_acquires_impl_NULL(x, ...) __try_acquires##__VA_ARGS__##_cap(0, x)
+
/**
* __cond_acquires() - function attribute, function conditionally
* acquires a capability exclusively
+ * @ret: abstract value returned by function if capability acquired
* @x: capability instance pointer
*
* Function attribute declaring that the function conditionally acquires the
- * given capability instance @x exclusively, but does not release it.
+ * given capability instance @x exclusively, but does not release it. The
+ * function return value @ret denotes when the capability is acquired.
+ *
+ * @ret may be one of: true, false, nonzero, 0, nonnull, NULL.
*/
-# define __cond_acquires(x) __try_acquires_cap(1, x)
+# define __cond_acquires(ret, x) __cond_acquires_impl_##ret(x)
/**
* __releases() - function attribute, function releases a capability exclusively
@@ -375,12 +392,16 @@ static inline void _capability_unsafe_alias(void **p) { }
/**
* __cond_acquires_shared() - function attribute, function conditionally
* acquires a capability shared
+ * @ret: abstract value returned by function if capability acquired
* @x: capability instance pointer
*
* Function attribute declaring that the function conditionally acquires the
- * given capability instance @x with shared access, but does not release it.
+ * given capability instance @x with shared access, but does not release it. The
+ * function return value @ret denotes when the capability is acquired.
+ *
+ * @ret may be one of: true, false, nonzero, 0, nonnull, NULL.
*/
-# define __cond_acquires_shared(x) __try_acquires_shared_cap(1, x)
+# define __cond_acquires_shared(ret, x) __cond_acquires_impl_##ret(x, _shared)
/**
* __releases_shared() - function attribute, function releases a
diff --git a/include/linux/refcount.h b/include/linux/refcount.h
index 80dc023ac2bf..3da377ffb0c2 100644
--- a/include/linux/refcount.h
+++ b/include/linux/refcount.h
@@ -478,9 +478,9 @@ static inline void refcount_dec(refcount_t *r)
extern __must_check bool refcount_dec_if_one(refcount_t *r);
extern __must_check bool refcount_dec_not_one(refcount_t *r);
-extern __must_check bool refcount_dec_and_mutex_lock(refcount_t *r, struct mutex *lock) __cond_acquires(lock);
-extern __must_check bool refcount_dec_and_lock(refcount_t *r, spinlock_t *lock) __cond_acquires(lock);
+extern __must_check bool refcount_dec_and_mutex_lock(refcount_t *r, struct mutex *lock) __cond_acquires(true, lock);
+extern __must_check bool refcount_dec_and_lock(refcount_t *r, spinlock_t *lock) __cond_acquires(true, lock);
extern __must_check bool refcount_dec_and_lock_irqsave(refcount_t *r,
spinlock_t *lock,
- unsigned long *flags) __cond_acquires(lock);
+ unsigned long *flags) __cond_acquires(true, lock);
#endif /* _LINUX_REFCOUNT_H */
diff --git a/include/linux/spinlock.h b/include/linux/spinlock.h
index 7679f39071e9..22295a126c3a 100644
--- a/include/linux/spinlock.h
+++ b/include/linux/spinlock.h
@@ -362,7 +362,7 @@ static __always_inline void spin_lock_bh(spinlock_t *lock)
}
static __always_inline int spin_trylock(spinlock_t *lock)
- __cond_acquires(lock) __no_capability_analysis
+ __cond_acquires(true, lock) __no_capability_analysis
{
return raw_spin_trylock(&lock->rlock);
}
@@ -422,13 +422,13 @@ static __always_inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned lo
}
static __always_inline int spin_trylock_bh(spinlock_t *lock)
- __cond_acquires(lock) __no_capability_analysis
+ __cond_acquires(true, lock) __no_capability_analysis
{
return raw_spin_trylock_bh(&lock->rlock);
}
static __always_inline int spin_trylock_irq(spinlock_t *lock)
- __cond_acquires(lock) __no_capability_analysis
+ __cond_acquires(true, lock) __no_capability_analysis
{
return raw_spin_trylock_irq(&lock->rlock);
}
diff --git a/include/linux/spinlock_api_smp.h b/include/linux/spinlock_api_smp.h
index fab02d8bf0c9..a77b76003ebb 100644
--- a/include/linux/spinlock_api_smp.h
+++ b/include/linux/spinlock_api_smp.h
@@ -34,8 +34,8 @@ unsigned long __lockfunc _raw_spin_lock_irqsave(raw_spinlock_t *lock)
unsigned long __lockfunc
_raw_spin_lock_irqsave_nested(raw_spinlock_t *lock, int subclass)
__acquires(lock);
-int __lockfunc _raw_spin_trylock(raw_spinlock_t *lock) __cond_acquires(lock);
-int __lockfunc _raw_spin_trylock_bh(raw_spinlock_t *lock) __cond_acquires(lock);
+int __lockfunc _raw_spin_trylock(raw_spinlock_t *lock) __cond_acquires(true, lock);
+int __lockfunc _raw_spin_trylock_bh(raw_spinlock_t *lock) __cond_acquires(true, lock);
void __lockfunc _raw_spin_unlock(raw_spinlock_t *lock) __releases(lock);
void __lockfunc _raw_spin_unlock_bh(raw_spinlock_t *lock) __releases(lock);
void __lockfunc _raw_spin_unlock_irq(raw_spinlock_t *lock) __releases(lock);
@@ -84,7 +84,7 @@ _raw_spin_unlock_irqrestore(raw_spinlock_t *lock, unsigned long flags)
#endif
static inline int __raw_spin_trylock(raw_spinlock_t *lock)
- __cond_acquires(lock)
+ __cond_acquires(true, lock)
{
preempt_disable();
if (do_raw_spin_trylock(lock)) {
@@ -177,7 +177,7 @@ static inline void __raw_spin_unlock_bh(raw_spinlock_t *lock)
}
static inline int __raw_spin_trylock_bh(raw_spinlock_t *lock)
- __cond_acquires(lock)
+ __cond_acquires(true, lock)
{
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);
if (do_raw_spin_trylock(lock)) {
diff --git a/net/ipv4/tcp_sigpool.c b/net/ipv4/tcp_sigpool.c
index d8a4f192873a..10b2e5970c40 100644
--- a/net/ipv4/tcp_sigpool.c
+++ b/net/ipv4/tcp_sigpool.c
@@ -257,7 +257,7 @@ void tcp_sigpool_get(unsigned int id)
}
EXPORT_SYMBOL_GPL(tcp_sigpool_get);
-int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c) __cond_acquires(RCU_BH)
+int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c) __cond_acquires(0, RCU_BH)
{
struct crypto_ahash *hash;
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply related
* [PATCH v3 10/35] locking/mutex: Support Clang's capability analysis
From: Marco Elver @ 2025-09-18 13:59 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett,
Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn,
Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor,
Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa,
Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long,
kasan-dev, linux-crypto, linux-doc, linux-kbuild, linux-kernel,
linux-mm, linux-security-module, linux-sparse, llvm, rcu
In-Reply-To: <20250918140451.1289454-1-elver@google.com>
Add support for Clang's capability analysis for mutex.
Signed-off-by: Marco Elver <elver@google.com>
---
v3:
* Switch to DECLARE_LOCK_GUARD_1_ATTRS() (suggested by Peter)
* __assert -> __assume rename
---
.../dev-tools/capability-analysis.rst | 2 +-
include/linux/mutex.h | 35 +++++-----
include/linux/mutex_types.h | 4 +-
lib/test_capability-analysis.c | 64 +++++++++++++++++++
4 files changed, 87 insertions(+), 18 deletions(-)
diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 9abd7f62cf4e..89f9c991f7cf 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -81,7 +81,7 @@ Supported Kernel Primitives
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Currently the following synchronization primitives are supported:
-`raw_spinlock_t`, `spinlock_t`, `rwlock_t`.
+`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`.
For capabilities with an initialization function (e.g., `spin_lock_init()`),
calling this function on the capability instance before initializing any
diff --git a/include/linux/mutex.h b/include/linux/mutex.h
index 847b81ca6436..7e4eb778d269 100644
--- a/include/linux/mutex.h
+++ b/include/linux/mutex.h
@@ -62,6 +62,7 @@ do { \
static struct lock_class_key __key; \
\
__mutex_init((mutex), #mutex, &__key); \
+ __assume_cap(mutex); \
} while (0)
/**
@@ -157,13 +158,13 @@ static inline int __must_check __devm_mutex_init(struct device *dev, struct mute
* Also see Documentation/locking/mutex-design.rst.
*/
#ifdef CONFIG_DEBUG_LOCK_ALLOC
-extern void mutex_lock_nested(struct mutex *lock, unsigned int subclass);
+extern void mutex_lock_nested(struct mutex *lock, unsigned int subclass) __acquires(lock);
extern void _mutex_lock_nest_lock(struct mutex *lock, struct lockdep_map *nest_lock);
extern int __must_check mutex_lock_interruptible_nested(struct mutex *lock,
- unsigned int subclass);
+ unsigned int subclass) __cond_acquires(0, lock);
extern int __must_check _mutex_lock_killable(struct mutex *lock,
- unsigned int subclass, struct lockdep_map *nest_lock);
-extern void mutex_lock_io_nested(struct mutex *lock, unsigned int subclass);
+ unsigned int subclass, struct lockdep_map *nest_lock) __cond_acquires(0, lock);
+extern void mutex_lock_io_nested(struct mutex *lock, unsigned int subclass) __acquires(lock);
#define mutex_lock(lock) mutex_lock_nested(lock, 0)
#define mutex_lock_interruptible(lock) mutex_lock_interruptible_nested(lock, 0)
@@ -186,10 +187,10 @@ do { \
_mutex_lock_killable(lock, subclass, NULL)
#else
-extern void mutex_lock(struct mutex *lock);
-extern int __must_check mutex_lock_interruptible(struct mutex *lock);
-extern int __must_check mutex_lock_killable(struct mutex *lock);
-extern void mutex_lock_io(struct mutex *lock);
+extern void mutex_lock(struct mutex *lock) __acquires(lock);
+extern int __must_check mutex_lock_interruptible(struct mutex *lock) __cond_acquires(0, lock);
+extern int __must_check mutex_lock_killable(struct mutex *lock) __cond_acquires(0, lock);
+extern void mutex_lock_io(struct mutex *lock) __acquires(lock);
# define mutex_lock_nested(lock, subclass) mutex_lock(lock)
# define mutex_lock_interruptible_nested(lock, subclass) mutex_lock_interruptible(lock)
@@ -207,7 +208,7 @@ extern void mutex_lock_io(struct mutex *lock);
*/
#ifdef CONFIG_DEBUG_LOCK_ALLOC
-extern int _mutex_trylock_nest_lock(struct mutex *lock, struct lockdep_map *nest_lock);
+extern int _mutex_trylock_nest_lock(struct mutex *lock, struct lockdep_map *nest_lock) __cond_acquires(true, lock);
#define mutex_trylock_nest_lock(lock, nest_lock) \
( \
@@ -217,17 +218,21 @@ extern int _mutex_trylock_nest_lock(struct mutex *lock, struct lockdep_map *nest
#define mutex_trylock(lock) _mutex_trylock_nest_lock(lock, NULL)
#else
-extern int mutex_trylock(struct mutex *lock);
+extern int mutex_trylock(struct mutex *lock) __cond_acquires(true, lock);
#define mutex_trylock_nest_lock(lock, nest_lock) mutex_trylock(lock)
#endif
-extern void mutex_unlock(struct mutex *lock);
+extern void mutex_unlock(struct mutex *lock) __releases(lock);
-extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);
+extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock) __cond_acquires(true, lock);
-DEFINE_GUARD(mutex, struct mutex *, mutex_lock(_T), mutex_unlock(_T))
-DEFINE_GUARD_COND(mutex, _try, mutex_trylock(_T))
-DEFINE_GUARD_COND(mutex, _intr, mutex_lock_interruptible(_T), _RET == 0)
+DEFINE_LOCK_GUARD_1(mutex, struct mutex, mutex_lock(_T->lock), mutex_unlock(_T->lock))
+DEFINE_LOCK_GUARD_1_COND(mutex, _try, mutex_trylock(_T->lock))
+DEFINE_LOCK_GUARD_1_COND(mutex, _intr, mutex_lock_interruptible(_T->lock), _RET == 0)
+
+DECLARE_LOCK_GUARD_1_ATTRS(mutex, __assumes_cap(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(mutex_try, __assumes_cap(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(mutex_intr, __assumes_cap(_T), /* */)
extern unsigned long mutex_get_owner(struct mutex *lock);
diff --git a/include/linux/mutex_types.h b/include/linux/mutex_types.h
index fdf7f515fde8..e1a5ea12d53c 100644
--- a/include/linux/mutex_types.h
+++ b/include/linux/mutex_types.h
@@ -38,7 +38,7 @@
* - detects multi-task circular deadlocks and prints out all affected
* locks and tasks (and only those tasks)
*/
-struct mutex {
+struct_with_capability(mutex) {
atomic_long_t owner;
raw_spinlock_t wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
@@ -59,7 +59,7 @@ struct mutex {
*/
#include <linux/rtmutex.h>
-struct mutex {
+struct_with_capability(mutex) {
struct rt_mutex_base rtmutex;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index 84060bace61d..286723b47328 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -5,6 +5,7 @@
*/
#include <linux/build_bug.h>
+#include <linux/mutex.h>
#include <linux/spinlock.h>
/*
@@ -144,3 +145,66 @@ TEST_SPINLOCK_COMMON(read_lock,
read_unlock,
read_trylock,
TEST_OP_RO);
+
+struct test_mutex_data {
+ struct mutex mtx;
+ int counter __guarded_by(&mtx);
+};
+
+static void __used test_mutex_init(struct test_mutex_data *d)
+{
+ mutex_init(&d->mtx);
+ d->counter = 0;
+}
+
+static void __used test_mutex_lock(struct test_mutex_data *d)
+{
+ mutex_lock(&d->mtx);
+ d->counter++;
+ mutex_unlock(&d->mtx);
+ mutex_lock_io(&d->mtx);
+ d->counter++;
+ mutex_unlock(&d->mtx);
+}
+
+static void __used test_mutex_trylock(struct test_mutex_data *d, atomic_t *a)
+{
+ if (!mutex_lock_interruptible(&d->mtx)) {
+ d->counter++;
+ mutex_unlock(&d->mtx);
+ }
+ if (!mutex_lock_killable(&d->mtx)) {
+ d->counter++;
+ mutex_unlock(&d->mtx);
+ }
+ if (mutex_trylock(&d->mtx)) {
+ d->counter++;
+ mutex_unlock(&d->mtx);
+ }
+ if (atomic_dec_and_mutex_lock(a, &d->mtx)) {
+ d->counter++;
+ mutex_unlock(&d->mtx);
+ }
+}
+
+static void __used test_mutex_assert(struct test_mutex_data *d)
+{
+ lockdep_assert_held(&d->mtx);
+ d->counter++;
+}
+
+static void __used test_mutex_guard(struct test_mutex_data *d)
+{
+ guard(mutex)(&d->mtx);
+ d->counter++;
+}
+
+static void __used test_mutex_cond_guard(struct test_mutex_data *d)
+{
+ scoped_cond_guard(mutex_try, return, &d->mtx) {
+ d->counter++;
+ }
+ scoped_cond_guard(mutex_intr, return, &d->mtx) {
+ d->counter++;
+ }
+}
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply related
* [PATCH v3 11/35] locking/seqlock: Support Clang's capability analysis
From: Marco Elver @ 2025-09-18 13:59 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett,
Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn,
Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor,
Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa,
Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long,
kasan-dev, linux-crypto, linux-doc, linux-kbuild, linux-kernel,
linux-mm, linux-security-module, linux-sparse, llvm, rcu
In-Reply-To: <20250918140451.1289454-1-elver@google.com>
Add support for Clang's capability analysis for seqlock_t.
Signed-off-by: Marco Elver <elver@google.com>
---
v3:
* __assert -> __assume rename
---
.../dev-tools/capability-analysis.rst | 2 +-
include/linux/seqlock.h | 24 +++++++++++
include/linux/seqlock_types.h | 5 ++-
lib/test_capability-analysis.c | 43 +++++++++++++++++++
4 files changed, 71 insertions(+), 3 deletions(-)
diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 89f9c991f7cf..4789de7b019a 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -81,7 +81,7 @@ Supported Kernel Primitives
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Currently the following synchronization primitives are supported:
-`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`.
+`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`.
For capabilities with an initialization function (e.g., `spin_lock_init()`),
calling this function on the capability instance before initializing any
diff --git a/include/linux/seqlock.h b/include/linux/seqlock.h
index 5ce48eab7a2a..2c7a02b727de 100644
--- a/include/linux/seqlock.h
+++ b/include/linux/seqlock.h
@@ -816,6 +816,7 @@ static __always_inline void write_seqcount_latch_end(seqcount_latch_t *s)
do { \
spin_lock_init(&(sl)->lock); \
seqcount_spinlock_init(&(sl)->seqcount, &(sl)->lock); \
+ __assume_cap(sl); \
} while (0)
/**
@@ -832,6 +833,7 @@ static __always_inline void write_seqcount_latch_end(seqcount_latch_t *s)
* Return: count, to be passed to read_seqretry()
*/
static inline unsigned read_seqbegin(const seqlock_t *sl)
+ __acquires_shared(sl) __no_capability_analysis
{
return read_seqcount_begin(&sl->seqcount);
}
@@ -848,6 +850,7 @@ static inline unsigned read_seqbegin(const seqlock_t *sl)
* Return: true if a read section retry is required, else false
*/
static inline unsigned read_seqretry(const seqlock_t *sl, unsigned start)
+ __releases_shared(sl) __no_capability_analysis
{
return read_seqcount_retry(&sl->seqcount, start);
}
@@ -872,6 +875,7 @@ static inline unsigned read_seqretry(const seqlock_t *sl, unsigned start)
* _irqsave or _bh variants of this function instead.
*/
static inline void write_seqlock(seqlock_t *sl)
+ __acquires(sl) __no_capability_analysis
{
spin_lock(&sl->lock);
do_write_seqcount_begin(&sl->seqcount.seqcount);
@@ -885,6 +889,7 @@ static inline void write_seqlock(seqlock_t *sl)
* critical section of given seqlock_t.
*/
static inline void write_sequnlock(seqlock_t *sl)
+ __releases(sl) __no_capability_analysis
{
do_write_seqcount_end(&sl->seqcount.seqcount);
spin_unlock(&sl->lock);
@@ -898,6 +903,7 @@ static inline void write_sequnlock(seqlock_t *sl)
* other write side sections, can be invoked from softirq contexts.
*/
static inline void write_seqlock_bh(seqlock_t *sl)
+ __acquires(sl) __no_capability_analysis
{
spin_lock_bh(&sl->lock);
do_write_seqcount_begin(&sl->seqcount.seqcount);
@@ -912,6 +918,7 @@ static inline void write_seqlock_bh(seqlock_t *sl)
* write_seqlock_bh().
*/
static inline void write_sequnlock_bh(seqlock_t *sl)
+ __releases(sl) __no_capability_analysis
{
do_write_seqcount_end(&sl->seqcount.seqcount);
spin_unlock_bh(&sl->lock);
@@ -925,6 +932,7 @@ static inline void write_sequnlock_bh(seqlock_t *sl)
* other write sections, can be invoked from hardirq contexts.
*/
static inline void write_seqlock_irq(seqlock_t *sl)
+ __acquires(sl) __no_capability_analysis
{
spin_lock_irq(&sl->lock);
do_write_seqcount_begin(&sl->seqcount.seqcount);
@@ -938,12 +946,14 @@ static inline void write_seqlock_irq(seqlock_t *sl)
* seqlock_t write side section opened with write_seqlock_irq().
*/
static inline void write_sequnlock_irq(seqlock_t *sl)
+ __releases(sl) __no_capability_analysis
{
do_write_seqcount_end(&sl->seqcount.seqcount);
spin_unlock_irq(&sl->lock);
}
static inline unsigned long __write_seqlock_irqsave(seqlock_t *sl)
+ __acquires(sl) __no_capability_analysis
{
unsigned long flags;
@@ -976,6 +986,7 @@ static inline unsigned long __write_seqlock_irqsave(seqlock_t *sl)
*/
static inline void
write_sequnlock_irqrestore(seqlock_t *sl, unsigned long flags)
+ __releases(sl) __no_capability_analysis
{
do_write_seqcount_end(&sl->seqcount.seqcount);
spin_unlock_irqrestore(&sl->lock, flags);
@@ -998,6 +1009,7 @@ write_sequnlock_irqrestore(seqlock_t *sl, unsigned long flags)
* The opened read section must be closed with read_sequnlock_excl().
*/
static inline void read_seqlock_excl(seqlock_t *sl)
+ __acquires_shared(sl) __no_capability_analysis
{
spin_lock(&sl->lock);
}
@@ -1007,6 +1019,7 @@ static inline void read_seqlock_excl(seqlock_t *sl)
* @sl: Pointer to seqlock_t
*/
static inline void read_sequnlock_excl(seqlock_t *sl)
+ __releases_shared(sl) __no_capability_analysis
{
spin_unlock(&sl->lock);
}
@@ -1021,6 +1034,7 @@ static inline void read_sequnlock_excl(seqlock_t *sl)
* from softirq contexts.
*/
static inline void read_seqlock_excl_bh(seqlock_t *sl)
+ __acquires_shared(sl) __no_capability_analysis
{
spin_lock_bh(&sl->lock);
}
@@ -1031,6 +1045,7 @@ static inline void read_seqlock_excl_bh(seqlock_t *sl)
* @sl: Pointer to seqlock_t
*/
static inline void read_sequnlock_excl_bh(seqlock_t *sl)
+ __releases_shared(sl) __no_capability_analysis
{
spin_unlock_bh(&sl->lock);
}
@@ -1045,6 +1060,7 @@ static inline void read_sequnlock_excl_bh(seqlock_t *sl)
* hardirq context.
*/
static inline void read_seqlock_excl_irq(seqlock_t *sl)
+ __acquires_shared(sl) __no_capability_analysis
{
spin_lock_irq(&sl->lock);
}
@@ -1055,11 +1071,13 @@ static inline void read_seqlock_excl_irq(seqlock_t *sl)
* @sl: Pointer to seqlock_t
*/
static inline void read_sequnlock_excl_irq(seqlock_t *sl)
+ __releases_shared(sl) __no_capability_analysis
{
spin_unlock_irq(&sl->lock);
}
static inline unsigned long __read_seqlock_excl_irqsave(seqlock_t *sl)
+ __acquires_shared(sl) __no_capability_analysis
{
unsigned long flags;
@@ -1089,6 +1107,7 @@ static inline unsigned long __read_seqlock_excl_irqsave(seqlock_t *sl)
*/
static inline void
read_sequnlock_excl_irqrestore(seqlock_t *sl, unsigned long flags)
+ __releases_shared(sl) __no_capability_analysis
{
spin_unlock_irqrestore(&sl->lock, flags);
}
@@ -1125,6 +1144,7 @@ read_sequnlock_excl_irqrestore(seqlock_t *sl, unsigned long flags)
* parameter of the next read_seqbegin_or_lock() iteration.
*/
static inline void read_seqbegin_or_lock(seqlock_t *lock, int *seq)
+ __acquires_shared(lock) __no_capability_analysis
{
if (!(*seq & 1)) /* Even */
*seq = read_seqbegin(lock);
@@ -1140,6 +1160,7 @@ static inline void read_seqbegin_or_lock(seqlock_t *lock, int *seq)
* Return: true if a read section retry is required, false otherwise
*/
static inline int need_seqretry(seqlock_t *lock, int seq)
+ __releases_shared(lock) __no_capability_analysis
{
return !(seq & 1) && read_seqretry(lock, seq);
}
@@ -1153,6 +1174,7 @@ static inline int need_seqretry(seqlock_t *lock, int seq)
* with read_seqbegin_or_lock() and validated by need_seqretry().
*/
static inline void done_seqretry(seqlock_t *lock, int seq)
+ __no_capability_analysis
{
if (seq & 1)
read_sequnlock_excl(lock);
@@ -1180,6 +1202,7 @@ static inline void done_seqretry(seqlock_t *lock, int seq)
*/
static inline unsigned long
read_seqbegin_or_lock_irqsave(seqlock_t *lock, int *seq)
+ __acquires_shared(lock) __no_capability_analysis
{
unsigned long flags = 0;
@@ -1205,6 +1228,7 @@ read_seqbegin_or_lock_irqsave(seqlock_t *lock, int *seq)
*/
static inline void
done_seqretry_irqrestore(seqlock_t *lock, int seq, unsigned long flags)
+ __no_capability_analysis
{
if (seq & 1)
read_sequnlock_excl_irqrestore(lock, flags);
diff --git a/include/linux/seqlock_types.h b/include/linux/seqlock_types.h
index dfdf43e3fa3d..9775d6f1a234 100644
--- a/include/linux/seqlock_types.h
+++ b/include/linux/seqlock_types.h
@@ -81,13 +81,14 @@ SEQCOUNT_LOCKNAME(mutex, struct mutex, true, mutex)
* - Comments on top of seqcount_t
* - Documentation/locking/seqlock.rst
*/
-typedef struct {
+struct_with_capability(seqlock) {
/*
* Make sure that readers don't starve writers on PREEMPT_RT: use
* seqcount_spinlock_t instead of seqcount_t. Check __SEQ_LOCK().
*/
seqcount_spinlock_t seqcount;
spinlock_t lock;
-} seqlock_t;
+};
+typedef struct seqlock seqlock_t;
#endif /* __LINUX_SEQLOCK_TYPES_H */
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index 286723b47328..74d287740bb8 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -6,6 +6,7 @@
#include <linux/build_bug.h>
#include <linux/mutex.h>
+#include <linux/seqlock.h>
#include <linux/spinlock.h>
/*
@@ -208,3 +209,45 @@ static void __used test_mutex_cond_guard(struct test_mutex_data *d)
d->counter++;
}
}
+
+struct test_seqlock_data {
+ seqlock_t sl;
+ int counter __guarded_by(&sl);
+};
+
+static void __used test_seqlock_init(struct test_seqlock_data *d)
+{
+ seqlock_init(&d->sl);
+ d->counter = 0;
+}
+
+static void __used test_seqlock_reader(struct test_seqlock_data *d)
+{
+ unsigned int seq;
+
+ do {
+ seq = read_seqbegin(&d->sl);
+ (void)d->counter;
+ } while (read_seqretry(&d->sl, seq));
+}
+
+static void __used test_seqlock_writer(struct test_seqlock_data *d)
+{
+ unsigned long flags;
+
+ write_seqlock(&d->sl);
+ d->counter++;
+ write_sequnlock(&d->sl);
+
+ write_seqlock_irq(&d->sl);
+ d->counter++;
+ write_sequnlock_irq(&d->sl);
+
+ write_seqlock_bh(&d->sl);
+ d->counter++;
+ write_sequnlock_bh(&d->sl);
+
+ write_seqlock_irqsave(&d->sl, flags);
+ d->counter++;
+ write_sequnlock_irqrestore(&d->sl, flags);
+}
--
2.51.0.384.g4c02a37b29-goog
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox