Commit 9432e90d authored by Kent Overstreet's avatar Kent Overstreet
Browse files

bcachefs: Check for invalid bucket from bucket_gen(), gc_bucket()



Turn more asserts into proper recoverable error paths.

Reported-by: default avatar <syzbot+246b47da27f8e7e7d6fb@syzkaller.appspotmail.com>
Signed-off-by: default avatarKent Overstreet <kent.overstreet@linux.dev>
parent 9c4acd19
Loading
Loading
Loading
Loading
+20 −2
Original line number Diff line number Diff line
@@ -741,6 +741,7 @@ int bch2_trigger_alloc(struct btree_trans *trans,
		       enum btree_iter_update_trigger_flags flags)
{
	struct bch_fs *c = trans->c;
	struct printbuf buf = PRINTBUF;
	int ret = 0;

	struct bch_dev *ca = bch2_dev_bucket_tryget(c, new.k->p);
@@ -860,8 +861,14 @@ int bch2_trigger_alloc(struct btree_trans *trans,
		}

		percpu_down_read(&c->mark_lock);
		if (new_a->gen != old_a->gen)
			*bucket_gen(ca, new.k->p.offset) = new_a->gen;
		if (new_a->gen != old_a->gen) {
			u8 *gen = bucket_gen(ca, new.k->p.offset);
			if (unlikely(!gen)) {
				percpu_up_read(&c->mark_lock);
				goto invalid_bucket;
			}
			*gen = new_a->gen;
		}

		bch2_dev_usage_update(c, ca, old_a, new_a, journal_seq, false);
		percpu_up_read(&c->mark_lock);
@@ -895,6 +902,11 @@ int bch2_trigger_alloc(struct btree_trans *trans,

		percpu_down_read(&c->mark_lock);
		struct bucket *g = gc_bucket(ca, new.k->p.offset);
		if (unlikely(!g)) {
			percpu_up_read(&c->mark_lock);
			goto invalid_bucket;
		}
		g->gen_valid	= 1;

		bucket_lock(g);

@@ -910,8 +922,14 @@ int bch2_trigger_alloc(struct btree_trans *trans,
		percpu_up_read(&c->mark_lock);
	}
err:
	printbuf_exit(&buf);
	bch2_dev_put(ca);
	return ret;
invalid_bucket:
	bch2_fs_inconsistent(c, "reference to invalid bucket\n  %s",
			     (bch2_bkey_val_to_text(&buf, c, new.s_c), buf.buf));
	ret = -EIO;
	goto err;
}

/*
+10 −5
Original line number Diff line number Diff line
@@ -874,6 +874,9 @@ static int bch2_alloc_write_key(struct btree_trans *trans,
	const struct bch_alloc_v4 *old;
	int ret;

	if (!bucket_valid(ca, k.k->p.offset))
		return 0;

	old = bch2_alloc_to_v4(k, &old_convert);
	gc = new = *old;

@@ -1005,12 +1008,14 @@ static int bch2_gc_alloc_start(struct bch_fs *c)
				continue;
			}

			if (bucket_valid(ca, k.k->p.offset)) {
				struct bch_alloc_v4 a_convert;
				const struct bch_alloc_v4 *a = bch2_alloc_to_v4(k, &a_convert);

				struct bucket *g = gc_bucket(ca, k.k->p.offset);
				g->gen_valid	= 1;
				g->gen		= a->gen;
			}
			0;
		})));
	bch2_dev_put(ca);
+35 −15
Original line number Diff line number Diff line
@@ -488,6 +488,17 @@ static int bch2_check_fix_ptr(struct btree_trans *trans,
	}

	struct bucket *g = PTR_GC_BUCKET(ca, &p.ptr);
	if (!g) {
		if (fsck_err(c, ptr_to_invalid_device,
			     "pointer to invalid bucket on device %u\n"
			     "while marking %s",
			     p.ptr.dev,
			     (printbuf_reset(&buf),
			      bch2_bkey_val_to_text(&buf, c, k), buf.buf)))
			*do_update = true;
		goto out;
	}

	enum bch_data_type data_type = bch2_bkey_ptr_data_type(k, p, entry);

	if (fsck_err_on(!g->gen_valid,
@@ -577,8 +588,8 @@ static int bch2_check_fix_ptr(struct btree_trans *trans,
	if (p.has_ec) {
		struct gc_stripe *m = genradix_ptr(&c->gc_stripes, p.ec.idx);

		if (fsck_err_on(!m || !m->alive, c,
				ptr_to_missing_stripe,
		if (fsck_err_on(!m || !m->alive,
				c, ptr_to_missing_stripe,
				"pointer to nonexistent stripe %llu\n"
				"while marking %s",
				(u64) p.ec.idx,
@@ -586,8 +597,8 @@ static int bch2_check_fix_ptr(struct btree_trans *trans,
				 bch2_bkey_val_to_text(&buf, c, k), buf.buf)))
			*do_update = true;

		if (fsck_err_on(m && m->alive && !bch2_ptr_matches_stripe_m(m, p), c,
				ptr_to_incorrect_stripe,
		if (fsck_err_on(m && m->alive && !bch2_ptr_matches_stripe_m(m, p),
				c, ptr_to_incorrect_stripe,
				"pointer does not match stripe %llu\n"
				"while marking %s",
				(u64) p.ec.idx,
@@ -1004,6 +1015,7 @@ static int bch2_trigger_pointer(struct btree_trans *trans,
			enum btree_iter_update_trigger_flags flags)
{
	bool insert = !(flags & BTREE_TRIGGER_overwrite);
	struct printbuf buf = PRINTBUF;
	int ret = 0;

	struct bch_fs *c = trans->c;
@@ -1036,6 +1048,13 @@ static int bch2_trigger_pointer(struct btree_trans *trans,
	if (flags & BTREE_TRIGGER_gc) {
		percpu_down_read(&c->mark_lock);
		struct bucket *g = gc_bucket(ca, bucket.offset);
		if (bch2_fs_inconsistent_on(!g, c, "reference to invalid bucket on device %u\n  %s",
					    p.ptr.dev,
					    (bch2_bkey_val_to_text(&buf, c, k), buf.buf))) {
			ret = -EIO;
			goto err_unlock;
		}

		bucket_lock(g);
		struct bch_alloc_v4 old = bucket_m_to_alloc(*g), new = old;
		ret = __mark_pointer(trans, ca, k, &p.ptr, *sectors, bp.data_type, &new);
@@ -1044,10 +1063,12 @@ static int bch2_trigger_pointer(struct btree_trans *trans,
			bch2_dev_usage_update(c, ca, &old, &new, 0, true);
		}
		bucket_unlock(g);
err_unlock:
		percpu_up_read(&c->mark_lock);
	}
err:
	bch2_dev_put(ca);
	printbuf_exit(&buf);
	return ret;
}

@@ -1335,10 +1356,11 @@ static int bch2_mark_metadata_bucket(struct bch_fs *c, struct bch_dev *ca,
			u64 b, enum bch_data_type data_type, unsigned sectors,
			enum btree_iter_update_trigger_flags flags)
{
	int ret = 0;

	percpu_down_read(&c->mark_lock);
	struct bucket *g = gc_bucket(ca, b);
	if (bch2_fs_inconsistent_on(!g, c, "reference to invalid bucket on device %u when marking metadata type %s",
				    ca->dev_idx, bch2_data_type_str(data_type)))
		goto err_unlock;

	bucket_lock(g);
	struct bch_alloc_v4 old = bucket_m_to_alloc(*g);
@@ -1347,29 +1369,27 @@ static int bch2_mark_metadata_bucket(struct bch_fs *c, struct bch_dev *ca,
			g->data_type != data_type, c,
			"different types of data in same bucket: %s, %s",
			bch2_data_type_str(g->data_type),
			bch2_data_type_str(data_type))) {
		ret = -EIO;
			bch2_data_type_str(data_type)))
		goto err;
	}

	if (bch2_fs_inconsistent_on((u64) g->dirty_sectors + sectors > ca->mi.bucket_size, c,
			"bucket %u:%llu gen %u data type %s sector count overflow: %u + %u > bucket size",
			ca->dev_idx, b, g->gen,
			bch2_data_type_str(g->data_type ?: data_type),
			g->dirty_sectors, sectors)) {
		ret = -EIO;
			g->dirty_sectors, sectors))
		goto err;
	}

	g->data_type = data_type;
	g->dirty_sectors += sectors;
	struct bch_alloc_v4 new = bucket_m_to_alloc(*g);
	bch2_dev_usage_update(c, ca, &old, &new, 0, true);
	percpu_up_read(&c->mark_lock);
	return 0;
err:
	bucket_unlock(g);
	if (!ret)
		bch2_dev_usage_update(c, ca, &old, &new, 0, true);
err_unlock:
	percpu_up_read(&c->mark_lock);
	return ret;
	return -EIO;
}

int bch2_trans_mark_metadata_bucket(struct btree_trans *trans,
+7 −4
Original line number Diff line number Diff line
@@ -172,19 +172,22 @@ static inline int gen_after(u8 a, u8 b)
	return r > 0 ? r : 0;
}

static inline u8 dev_ptr_stale_rcu(struct bch_dev *ca, const struct bch_extent_ptr *ptr)
static inline int dev_ptr_stale_rcu(struct bch_dev *ca, const struct bch_extent_ptr *ptr)
{
	return gen_after(*bucket_gen(ca, PTR_BUCKET_NR(ca, ptr)), ptr->gen);
	u8 *gen = bucket_gen(ca, PTR_BUCKET_NR(ca, ptr));
	if (!gen)
		return -1;
	return gen_after(*gen, ptr->gen);
}

/**
 * dev_ptr_stale() - check if a pointer points into a bucket that has been
 * invalidated.
 */
static inline u8 dev_ptr_stale(struct bch_dev *ca, const struct bch_extent_ptr *ptr)
static inline int dev_ptr_stale(struct bch_dev *ca, const struct bch_extent_ptr *ptr)
{
	rcu_read_lock();
	u8 ret = dev_ptr_stale_rcu(ca, ptr);
	int ret = dev_ptr_stale_rcu(ca, ptr);
	rcu_read_unlock();

	return ret;
+20 −6
Original line number Diff line number Diff line
@@ -268,6 +268,7 @@ static int mark_stripe_bucket(struct btree_trans *trans,
{
	struct bch_fs *c = trans->c;
	const struct bch_extent_ptr *ptr = s.v->ptrs + ptr_idx;
	struct printbuf buf = PRINTBUF;
	int ret = 0;

	struct bch_dev *ca = bch2_dev_tryget(c, ptr->dev);
@@ -289,6 +290,13 @@ static int mark_stripe_bucket(struct btree_trans *trans,
	if (flags & BTREE_TRIGGER_gc) {
		percpu_down_read(&c->mark_lock);
		struct bucket *g = gc_bucket(ca, bucket.offset);
		if (bch2_fs_inconsistent_on(!g, c, "reference to invalid bucket on device %u\n  %s",
					    ptr->dev,
					    (bch2_bkey_val_to_text(&buf, c, s.s_c), buf.buf))) {
			ret = -EIO;
			goto err_unlock;
		}

		bucket_lock(g);
		struct bch_alloc_v4 old = bucket_m_to_alloc(*g), new = old;
		ret = __mark_stripe_bucket(trans, ca, s, ptr_idx, deleting, bucket, &new, flags);
@@ -297,10 +305,12 @@ static int mark_stripe_bucket(struct btree_trans *trans,
			bch2_dev_usage_update(c, ca, &old, &new, 0, true);
		}
		bucket_unlock(g);
err_unlock:
		percpu_up_read(&c->mark_lock);
	}
err:
	bch2_dev_put(ca);
	printbuf_exit(&buf);
	return ret;
}

@@ -714,10 +724,12 @@ static void ec_block_endio(struct bio *bio)
			       bch2_blk_status_to_str(bio->bi_status)))
		clear_bit(ec_bio->idx, ec_bio->buf->valid);

	if (dev_ptr_stale(ca, ptr)) {
	int stale = dev_ptr_stale(ca, ptr);
	if (stale) {
		bch_err_ratelimited(ca->fs,
				    "error %s stripe: stale pointer after io",
				    bio_data_dir(bio) == READ ? "reading from" : "writing to");
				    "error %s stripe: stale/invalid pointer (%i) after io",
				    bio_data_dir(bio) == READ ? "reading from" : "writing to",
				    stale);
		clear_bit(ec_bio->idx, ec_bio->buf->valid);
	}

@@ -743,10 +755,12 @@ static void ec_block_io(struct bch_fs *c, struct ec_stripe_buf *buf,
		return;
	}

	if (dev_ptr_stale(ca, ptr)) {
	int stale = dev_ptr_stale(ca, ptr);
	if (stale) {
		bch_err_ratelimited(c,
				    "error %s stripe: stale pointer",
				    rw == READ ? "reading from" : "writing to");
				    "error %s stripe: stale pointer (%i)",
				    rw == READ ? "reading from" : "writing to",
				    stale);
		clear_bit(idx, buf->valid);
		return;
	}
Loading