summaryrefslogtreecommitdiffstats
path: root/fs/bcachefs/bkey.c
diff options
context:
space:
mode:
authorKent Overstreet <kent.overstreet@linux.dev>2023-08-03 16:38:36 -0400
committerKent Overstreet <kent.overstreet@linux.dev>2023-10-22 17:10:09 -0400
commit77212d3a76a4becabeac8736b686a533dd75913b (patch)
treeb30b3578d6cf6c818d8556ef963cfcc789885655 /fs/bcachefs/bkey.c
parent6c6439650ec913c83d48055da63b8f204075afb7 (diff)
downloadlinux-77212d3a76a4becabeac8736b686a533dd75913b.tar.gz
linux-77212d3a76a4becabeac8736b686a533dd75913b.tar.bz2
linux-77212d3a76a4becabeac8736b686a533dd75913b.zip
bcachefs: Fix shift by 64 in set_inc_field()
UBSAN was complaining about a shift by 64 in set_inc_field(). This only happened when the value being shifted was 0, so in theory should be harmless - a shift by 64 (or register width) should logically give a result of 0, but CPUs will in practice leave the input unchanged when the number of bits to shift by wraps - and since our input here is 0, the output is still what we want. But, it's still undefined behaviour and we need our UBSAN output to be clean, so it needs to be fixed. Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
Diffstat (limited to 'fs/bcachefs/bkey.c')
-rw-r--r--fs/bcachefs/bkey.c51
1 files changed, 24 insertions, 27 deletions
diff --git a/fs/bcachefs/bkey.c b/fs/bcachefs/bkey.c
index b7b77f459724..34a8fe48581c 100644
--- a/fs/bcachefs/bkey.c
+++ b/fs/bcachefs/bkey.c
@@ -185,6 +185,28 @@ static u64 get_inc_field(struct unpack_state *state, unsigned field)
}
__always_inline
+static void __set_inc_field(struct pack_state *state, unsigned field, u64 v)
+{
+ unsigned bits = state->format->bits_per_field[field];
+
+ if (bits) {
+ if (bits > state->bits) {
+ bits -= state->bits;
+ /* avoid shift by 64 if bits is 64 - bits is never 0 here: */
+ state->w |= (v >> 1) >> (bits - 1);
+
+ *state->p = state->w;
+ state->p = next_word(state->p);
+ state->w = 0;
+ state->bits = 64;
+ }
+
+ state->bits -= bits;
+ state->w |= v << state->bits;
+ }
+}
+
+__always_inline
static bool set_inc_field(struct pack_state *state, unsigned field, u64 v)
{
unsigned bits = state->format->bits_per_field[field];
@@ -198,20 +220,7 @@ static bool set_inc_field(struct pack_state *state, unsigned field, u64 v)
if (fls64(v) > bits)
return false;
- if (bits > state->bits) {
- bits -= state->bits;
- /* avoid shift by 64 if bits is 0 - bits is never 64 here: */
- state->w |= (v >> 1) >> (bits - 1);
-
- *state->p = state->w;
- state->p = next_word(state->p);
- state->w = 0;
- state->bits = 64;
- }
-
- state->bits -= bits;
- state->w |= v << state->bits;
-
+ __set_inc_field(state, field, v);
return true;
}
@@ -380,19 +389,7 @@ static bool set_inc_field_lossy(struct pack_state *state, unsigned field, u64 v)
ret = false;
}
- if (bits > state->bits) {
- bits -= state->bits;
- state->w |= (v >> 1) >> (bits - 1);
-
- *state->p = state->w;
- state->p = next_word(state->p);
- state->w = 0;
- state->bits = 64;
- }
-
- state->bits -= bits;
- state->w |= v << state->bits;
-
+ __set_inc_field(state, field, v);
return ret;
}