|
| 1 | +kyber: fix out of bounds access when preempted |
| 2 | + |
| 3 | +jira LE-3201 |
| 4 | +cve CVE-2021-46984 |
| 5 | +Rebuild_History Non-Buildable kernel-rt-4.18.0-553.22.1.rt7.363.el8_10 |
| 6 | +commit-author Omar Sandoval <osandov@fb.com> |
| 7 | +commit efed9a3337e341bd0989161b97453b52567bc59d |
| 8 | +Empty-Commit: Cherry-Pick Conflicts during history rebuild. |
| 9 | +Will be included in final tarball splat. Ref for failed cherry-pick at: |
| 10 | +ciq/ciq_backports/kernel-rt-4.18.0-553.22.1.rt7.363.el8_10/efed9a33.failed |
| 11 | + |
| 12 | +__blk_mq_sched_bio_merge() gets the ctx and hctx for the current CPU and |
| 13 | +passes the hctx to ->bio_merge(). kyber_bio_merge() then gets the ctx |
| 14 | +for the current CPU again and uses that to get the corresponding Kyber |
| 15 | +context in the passed hctx. However, the thread may be preempted between |
| 16 | +the two calls to blk_mq_get_ctx(), and the ctx returned the second time |
| 17 | +may no longer correspond to the passed hctx. This "works" accidentally |
| 18 | +most of the time, but it can cause us to read garbage if the second ctx |
| 19 | +came from an hctx with more ctx's than the first one (i.e., if |
| 20 | +ctx->index_hw[hctx->type] > hctx->nr_ctx). |
| 21 | + |
| 22 | +This manifested as this UBSAN array index out of bounds error reported |
| 23 | +by Jakub: |
| 24 | + |
| 25 | +UBSAN: array-index-out-of-bounds in ../kernel/locking/qspinlock.c:130:9 |
| 26 | +index 13106 is out of range for type 'long unsigned int [128]' |
| 27 | +Call Trace: |
| 28 | + dump_stack+0xa4/0xe5 |
| 29 | + ubsan_epilogue+0x5/0x40 |
| 30 | + __ubsan_handle_out_of_bounds.cold.13+0x2a/0x34 |
| 31 | + queued_spin_lock_slowpath+0x476/0x480 |
| 32 | + do_raw_spin_lock+0x1c2/0x1d0 |
| 33 | + kyber_bio_merge+0x112/0x180 |
| 34 | + blk_mq_submit_bio+0x1f5/0x1100 |
| 35 | + submit_bio_noacct+0x7b0/0x870 |
| 36 | + submit_bio+0xc2/0x3a0 |
| 37 | + btrfs_map_bio+0x4f0/0x9d0 |
| 38 | + btrfs_submit_data_bio+0x24e/0x310 |
| 39 | + submit_one_bio+0x7f/0xb0 |
| 40 | + submit_extent_page+0xc4/0x440 |
| 41 | + __extent_writepage_io+0x2b8/0x5e0 |
| 42 | + __extent_writepage+0x28d/0x6e0 |
| 43 | + extent_write_cache_pages+0x4d7/0x7a0 |
| 44 | + extent_writepages+0xa2/0x110 |
| 45 | + do_writepages+0x8f/0x180 |
| 46 | + __writeback_single_inode+0x99/0x7f0 |
| 47 | + writeback_sb_inodes+0x34e/0x790 |
| 48 | + __writeback_inodes_wb+0x9e/0x120 |
| 49 | + wb_writeback+0x4d2/0x660 |
| 50 | + wb_workfn+0x64d/0xa10 |
| 51 | + process_one_work+0x53a/0xa80 |
| 52 | + worker_thread+0x69/0x5b0 |
| 53 | + kthread+0x20b/0x240 |
| 54 | + ret_from_fork+0x1f/0x30 |
| 55 | + |
| 56 | +Only Kyber uses the hctx, so fix it by passing the request_queue to |
| 57 | +->bio_merge() instead. BFQ and mq-deadline just use that, and Kyber can |
| 58 | +map the queues itself to avoid the mismatch. |
| 59 | + |
| 60 | +Fixes: a6088845c2bf ("block: kyber: make kyber more friendly with merging") |
| 61 | + Reported-by: Jakub Kicinski <kuba@kernel.org> |
| 62 | + Signed-off-by: Omar Sandoval <osandov@fb.com> |
| 63 | +Link: https://lore.kernel.org/r/c7598605401a48d5cfeadebb678abd10af22b83f.1620691329.git.osandov@fb.com |
| 64 | + Signed-off-by: Jens Axboe <axboe@kernel.dk> |
| 65 | +(cherry picked from commit efed9a3337e341bd0989161b97453b52567bc59d) |
| 66 | + Signed-off-by: Jonathan Maple <jmaple@ciq.com> |
| 67 | + |
| 68 | +# Conflicts: |
| 69 | +# block/bfq-iosched.c |
| 70 | +# block/blk-mq-sched.c |
| 71 | +# block/kyber-iosched.c |
| 72 | +# block/mq-deadline.c |
| 73 | +# include/linux/elevator.h |
| 74 | +diff --cc block/bfq-iosched.c |
| 75 | +index c49d92b5ef8d,59b2499d3f8b..000000000000 |
| 76 | +--- a/block/bfq-iosched.c |
| 77 | ++++ b/block/bfq-iosched.c |
| 78 | +@@@ -2329,9 -2263,9 +2329,13 @@@ static void bfq_remove_request(struct r |
| 79 | + |
| 80 | + } |
| 81 | + |
| 82 | +++<<<<<<< HEAD |
| 83 | + +static bool bfq_bio_merge(struct blk_mq_hw_ctx *hctx, struct bio *bio) |
| 84 | +++======= |
| 85 | ++ static bool bfq_bio_merge(struct request_queue *q, struct bio *bio, |
| 86 | ++ unsigned int nr_segs) |
| 87 | +++>>>>>>> efed9a3337e3 (kyber: fix out of bounds access when preempted) |
| 88 | + { |
| 89 | +- struct request_queue *q = hctx->queue; |
| 90 | + struct bfq_data *bfqd = q->elevator->elevator_data; |
| 91 | + struct request *free = NULL; |
| 92 | + /* |
| 93 | +diff --cc block/blk-mq-sched.c |
| 94 | +index 8f52783b0eda,996a4b2f73aa..000000000000 |
| 95 | +--- a/block/blk-mq-sched.c |
| 96 | ++++ b/block/blk-mq-sched.c |
| 97 | +@@@ -370,79 -354,39 +370,85 @@@ void blk_mq_sched_dispatch_requests(str |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + -bool __blk_mq_sched_bio_merge(struct request_queue *q, struct bio *bio, |
| 102 | + - unsigned int nr_segs) |
| 103 | + +bool blk_mq_sched_try_merge(struct request_queue *q, struct bio *bio, |
| 104 | + + struct request **merged_request) |
| 105 | + +{ |
| 106 | + + struct request *rq; |
| 107 | + + |
| 108 | + + switch (elv_merge(q, &rq, bio)) { |
| 109 | + + case ELEVATOR_BACK_MERGE: |
| 110 | + + if (!blk_mq_sched_allow_merge(q, rq, bio)) |
| 111 | + + return false; |
| 112 | + + if (!bio_attempt_back_merge(q, rq, bio)) |
| 113 | + + return false; |
| 114 | + + *merged_request = attempt_back_merge(q, rq); |
| 115 | + + if (!*merged_request) |
| 116 | + + elv_merged_request(q, rq, ELEVATOR_BACK_MERGE); |
| 117 | + + return true; |
| 118 | + + case ELEVATOR_FRONT_MERGE: |
| 119 | + + if (!blk_mq_sched_allow_merge(q, rq, bio)) |
| 120 | + + return false; |
| 121 | + + if (!bio_attempt_front_merge(q, rq, bio)) |
| 122 | + + return false; |
| 123 | + + *merged_request = attempt_front_merge(q, rq); |
| 124 | + + if (!*merged_request) |
| 125 | + + elv_merged_request(q, rq, ELEVATOR_FRONT_MERGE); |
| 126 | + + return true; |
| 127 | + + case ELEVATOR_DISCARD_MERGE: |
| 128 | + + return bio_attempt_discard_merge(q, rq, bio); |
| 129 | + + default: |
| 130 | + + return false; |
| 131 | + + } |
| 132 | + +} |
| 133 | + +EXPORT_SYMBOL_GPL(blk_mq_sched_try_merge); |
| 134 | + + |
| 135 | + +/* |
| 136 | + + * Reverse check our software queue for entries that we could potentially |
| 137 | + + * merge with. Currently includes a hand-wavy stop count of 8, to not spend |
| 138 | + + * too much time checking for merges. |
| 139 | + + */ |
| 140 | + +static bool blk_mq_attempt_merge(struct request_queue *q, |
| 141 | + + struct blk_mq_hw_ctx *hctx, |
| 142 | + + struct blk_mq_ctx *ctx, struct bio *bio) |
| 143 | + +{ |
| 144 | + + enum hctx_type type = hctx->type; |
| 145 | + + |
| 146 | + + lockdep_assert_held(&ctx->lock); |
| 147 | + + |
| 148 | + + if (blk_bio_list_merge(q, &ctx->rq_lists[type], bio)) { |
| 149 | + + ctx->rq_merged++; |
| 150 | + + return true; |
| 151 | + + } |
| 152 | + + |
| 153 | + + return false; |
| 154 | + +} |
| 155 | + + |
| 156 | + +bool __blk_mq_sched_bio_merge(struct request_queue *q, struct bio *bio) |
| 157 | + { |
| 158 | + struct elevator_queue *e = q->elevator; |
| 159 | +- struct blk_mq_ctx *ctx = blk_mq_get_ctx(q); |
| 160 | +- struct blk_mq_hw_ctx *hctx = blk_mq_map_queue(q, bio->bi_opf, ctx); |
| 161 | ++ struct blk_mq_ctx *ctx; |
| 162 | ++ struct blk_mq_hw_ctx *hctx; |
| 163 | + bool ret = false; |
| 164 | + enum hctx_type type; |
| 165 | + |
| 166 | + if (e && e->type->ops.bio_merge) |
| 167 | +++<<<<<<< HEAD |
| 168 | + + return e->type->ops.bio_merge(hctx, bio); |
| 169 | +++======= |
| 170 | ++ return e->type->ops.bio_merge(q, bio, nr_segs); |
| 171 | +++>>>>>>> efed9a3337e3 (kyber: fix out of bounds access when preempted) |
| 172 | + |
| 173 | ++ ctx = blk_mq_get_ctx(q); |
| 174 | ++ hctx = blk_mq_map_queue(q, bio->bi_opf, ctx); |
| 175 | + type = hctx->type; |
| 176 | + - if (!(hctx->flags & BLK_MQ_F_SHOULD_MERGE) || |
| 177 | + - list_empty_careful(&ctx->rq_lists[type])) |
| 178 | + - return false; |
| 179 | + - |
| 180 | + - /* default per sw-queue merge */ |
| 181 | + - spin_lock(&ctx->lock); |
| 182 | + - /* |
| 183 | + - * Reverse check our software queue for entries that we could |
| 184 | + - * potentially merge with. Currently includes a hand-wavy stop |
| 185 | + - * count of 8, to not spend too much time checking for merges. |
| 186 | + - */ |
| 187 | + - if (blk_bio_list_merge(q, &ctx->rq_lists[type], bio, nr_segs)) { |
| 188 | + - ctx->rq_merged++; |
| 189 | + - ret = true; |
| 190 | + + if ((hctx->flags & BLK_MQ_F_SHOULD_MERGE) && |
| 191 | + + !list_empty_careful(&ctx->rq_lists[type])) { |
| 192 | + + /* default per sw-queue merge */ |
| 193 | + + spin_lock(&ctx->lock); |
| 194 | + + ret = blk_mq_attempt_merge(q, hctx, ctx, bio); |
| 195 | + + spin_unlock(&ctx->lock); |
| 196 | + } |
| 197 | + |
| 198 | + - spin_unlock(&ctx->lock); |
| 199 | + - |
| 200 | + return ret; |
| 201 | + } |
| 202 | + |
| 203 | +diff --cc block/kyber-iosched.c |
| 204 | +index 6df74a3a3cd3,81e3279ecd57..000000000000 |
| 205 | +--- a/block/kyber-iosched.c |
| 206 | ++++ b/block/kyber-iosched.c |
| 207 | +@@@ -572,10 -561,12 +572,16 @@@ static void kyber_limit_depth(unsigned |
| 208 | + } |
| 209 | + } |
| 210 | + |
| 211 | +++<<<<<<< HEAD |
| 212 | + +static bool kyber_bio_merge(struct blk_mq_hw_ctx *hctx, struct bio *bio) |
| 213 | +++======= |
| 214 | ++ static bool kyber_bio_merge(struct request_queue *q, struct bio *bio, |
| 215 | ++ unsigned int nr_segs) |
| 216 | +++>>>>>>> efed9a3337e3 (kyber: fix out of bounds access when preempted) |
| 217 | + { |
| 218 | ++ struct blk_mq_ctx *ctx = blk_mq_get_ctx(q); |
| 219 | ++ struct blk_mq_hw_ctx *hctx = blk_mq_map_queue(q, bio->bi_opf, ctx); |
| 220 | + struct kyber_hctx_data *khd = hctx->sched_data; |
| 221 | +- struct blk_mq_ctx *ctx = blk_mq_get_ctx(hctx->queue); |
| 222 | + struct kyber_ctx_queue *kcq = &khd->kcqs[ctx->index_hw[hctx->type]]; |
| 223 | + unsigned int sched_domain = kyber_sched_domain(bio->bi_opf); |
| 224 | + struct list_head *rq_list = &kcq->rq_list[sched_domain]; |
| 225 | +diff --cc block/mq-deadline.c |
| 226 | +index 43ce0ae88b4a,8eea2cbf2bf4..000000000000 |
| 227 | +--- a/block/mq-deadline.c |
| 228 | ++++ b/block/mq-deadline.c |
| 229 | +@@@ -631,13 -461,9 +631,17 @@@ static int dd_request_merge(struct requ |
| 230 | + return ELEVATOR_NO_MERGE; |
| 231 | + } |
| 232 | + |
| 233 | +++<<<<<<< HEAD |
| 234 | + +/* |
| 235 | + + * Attempt to merge a bio into an existing request. This function is called |
| 236 | + + * before @bio is associated with a request. |
| 237 | + + */ |
| 238 | + +static bool dd_bio_merge(struct blk_mq_hw_ctx *hctx, struct bio *bio) |
| 239 | +++======= |
| 240 | ++ static bool dd_bio_merge(struct request_queue *q, struct bio *bio, |
| 241 | ++ unsigned int nr_segs) |
| 242 | +++>>>>>>> efed9a3337e3 (kyber: fix out of bounds access when preempted) |
| 243 | + { |
| 244 | +- struct request_queue *q = hctx->queue; |
| 245 | + struct deadline_data *dd = q->elevator->elevator_data; |
| 246 | + struct request *free = NULL; |
| 247 | + bool ret; |
| 248 | +diff --cc include/linux/elevator.h |
| 249 | +index b4f3bc81ddd3,dcb2f9022c1d..000000000000 |
| 250 | +--- a/include/linux/elevator.h |
| 251 | ++++ b/include/linux/elevator.h |
| 252 | +@@@ -31,9 -31,10 +31,13 @@@ struct elevator_mq_ops |
| 253 | + void (*exit_sched)(struct elevator_queue *); |
| 254 | + int (*init_hctx)(struct blk_mq_hw_ctx *, unsigned int); |
| 255 | + void (*exit_hctx)(struct blk_mq_hw_ctx *, unsigned int); |
| 256 | + - void (*depth_updated)(struct blk_mq_hw_ctx *); |
| 257 | + |
| 258 | + bool (*allow_merge)(struct request_queue *, struct request *, struct bio *); |
| 259 | +++<<<<<<< HEAD |
| 260 | + + bool (*bio_merge)(struct blk_mq_hw_ctx *, struct bio *); |
| 261 | +++======= |
| 262 | ++ bool (*bio_merge)(struct request_queue *, struct bio *, unsigned int); |
| 263 | +++>>>>>>> efed9a3337e3 (kyber: fix out of bounds access when preempted) |
| 264 | + int (*request_merge)(struct request_queue *q, struct request **, struct bio *); |
| 265 | + void (*request_merged)(struct request_queue *, struct request *, enum elv_merge); |
| 266 | + void (*requests_merged)(struct request_queue *, struct request *, struct request *); |
| 267 | +* Unmerged path block/bfq-iosched.c |
| 268 | +* Unmerged path block/blk-mq-sched.c |
| 269 | +* Unmerged path block/kyber-iosched.c |
| 270 | +* Unmerged path block/mq-deadline.c |
| 271 | +* Unmerged path include/linux/elevator.h |
0 commit comments