Skip to content

Commit 8e43fb0

Browse files
alberandebiggers
authored andcommitted
fsverity: remove hash page spin lock
The spin lock is not necessary here as it can be replaced with memory barrier which should be better performance-wise. When Merkle tree block size differs from page size, in is_hash_block_verified() two things are modified during check - a bitmap and PG_checked flag of the page. Each bit in the bitmap represent verification status of the Merkle tree blocks. PG_checked flag tells if page was just re-instantiated or was in pagecache. Both of this states are shared between verification threads. Page which was re-instantiated can not have already verified blocks (bit set in bitmap). The spin lock was used to allow only one thread to modify both of these states and keep order of operations. The only requirement here is that PG_Checked is set strictly after bitmap is updated. This way other threads which see that PG_Checked=1 (page cached) knows that bitmap is up-to-date. Otherwise, if PG_Checked is set before bitmap is cleared, other threads can see bit=1 and therefore will not perform verification of that Merkle tree block. However, there's still the case when one thread is setting a bit in verify_data_block() and other thread is clearing it in is_hash_block_verified(). This can happen if two threads get to !PageChecked branch and one of the threads is rescheduled before resetting the bitmap. This is fine as at worst blocks are re-verified in each thread. Signed-off-by: Andrey Albershteyn <[email protected]> [ebiggers: improved the comment and removed the 'verified' variable] Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Eric Biggers <[email protected]>
1 parent 41bccc9 commit 8e43fb0

File tree

3 files changed

+24
-26
lines changed

3 files changed

+24
-26
lines changed

fs/verity/fsverity_private.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ struct fsverity_info {
6969
u8 file_digest[FS_VERITY_MAX_DIGEST_SIZE];
7070
const struct inode *inode;
7171
unsigned long *hash_block_verified;
72-
spinlock_t hash_page_init_lock;
7372
};
7473

7574
#define FS_VERITY_MAX_SIGNATURE_SIZE (FS_VERITY_MAX_DESCRIPTOR_SIZE - \

fs/verity/open.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ struct fsverity_info *fsverity_create_info(const struct inode *inode,
239239
err = -ENOMEM;
240240
goto fail;
241241
}
242-
spin_lock_init(&vi->hash_page_init_lock);
243242
}
244243

245244
return vi;

fs/verity/verify.c

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ static struct workqueue_struct *fsverity_read_workqueue;
1919
static bool is_hash_block_verified(struct fsverity_info *vi, struct page *hpage,
2020
unsigned long hblock_idx)
2121
{
22-
bool verified;
2322
unsigned int blocks_per_page;
2423
unsigned int i;
2524

@@ -43,12 +42,20 @@ static bool is_hash_block_verified(struct fsverity_info *vi, struct page *hpage,
4342
* re-instantiated from the backing storage are re-verified. To do
4443
* this, we use PG_checked again, but now it doesn't really mean
4544
* "checked". Instead, now it just serves as an indicator for whether
46-
* the hash page is newly instantiated or not.
45+
* the hash page is newly instantiated or not. If the page is new, as
46+
* indicated by PG_checked=0, we clear the bitmap bits for the page's
47+
* blocks since they are untrustworthy, then set PG_checked=1.
48+
* Otherwise we return the bitmap bit for the requested block.
4749
*
48-
* The first thread that sees PG_checked=0 must clear the corresponding
49-
* bitmap bits, then set PG_checked=1. This requires a spinlock. To
50-
* avoid having to take this spinlock in the common case of
51-
* PG_checked=1, we start with an opportunistic lockless read.
50+
* Multiple threads may execute this code concurrently on the same page.
51+
* This is safe because we use memory barriers to ensure that if a
52+
* thread sees PG_checked=1, then it also sees the associated bitmap
53+
* clearing to have occurred. Also, all writes and their corresponding
54+
* reads are atomic, and all writes are safe to repeat in the event that
55+
* multiple threads get into the PG_checked=0 section. (Clearing a
56+
* bitmap bit again at worst causes a hash block to be verified
57+
* redundantly. That event should be very rare, so it's not worth using
58+
* a lock to avoid. Setting PG_checked again has no effect.)
5259
*/
5360
if (PageChecked(hpage)) {
5461
/*
@@ -58,24 +65,17 @@ static bool is_hash_block_verified(struct fsverity_info *vi, struct page *hpage,
5865
smp_rmb();
5966
return test_bit(hblock_idx, vi->hash_block_verified);
6067
}
61-
spin_lock(&vi->hash_page_init_lock);
62-
if (PageChecked(hpage)) {
63-
verified = test_bit(hblock_idx, vi->hash_block_verified);
64-
} else {
65-
blocks_per_page = vi->tree_params.blocks_per_page;
66-
hblock_idx = round_down(hblock_idx, blocks_per_page);
67-
for (i = 0; i < blocks_per_page; i++)
68-
clear_bit(hblock_idx + i, vi->hash_block_verified);
69-
/*
70-
* A write memory barrier is needed here to give RELEASE
71-
* semantics to the below SetPageChecked() operation.
72-
*/
73-
smp_wmb();
74-
SetPageChecked(hpage);
75-
verified = false;
76-
}
77-
spin_unlock(&vi->hash_page_init_lock);
78-
return verified;
68+
blocks_per_page = vi->tree_params.blocks_per_page;
69+
hblock_idx = round_down(hblock_idx, blocks_per_page);
70+
for (i = 0; i < blocks_per_page; i++)
71+
clear_bit(hblock_idx + i, vi->hash_block_verified);
72+
/*
73+
* A write memory barrier is needed here to give RELEASE semantics to
74+
* the below SetPageChecked() operation.
75+
*/
76+
smp_wmb();
77+
SetPageChecked(hpage);
78+
return false;
7979
}
8080

8181
/*

0 commit comments

Comments
 (0)