Skip to content

Commit 1dfd062

Browse files
namjaejeonsmfrench
authored andcommitted
ksmbd: fix use-after-free by using call_rcu() for oplock_info
ksmbd currently frees oplock_info immediately using kfree(), even though it is accessed under RCU read-side critical sections in places like opinfo_get() and proc_show_files(). Since there is no RCU grace period delay between nullifying the pointer and freeing the memory, a reader can still access oplock_info structure after it has been freed. This can leads to a use-after-free especially in opinfo_get() where atomic_inc_not_zero() is called on already freed memory. Fix this by switching to deferred freeing using call_rcu(). Fixes: 18b4fac ("ksmbd: fix use-after-free in smb_break_all_levII_oplock()") Cc: [email protected] Signed-off-by: Namjae Jeon <[email protected]> Signed-off-by: Steve French <[email protected]>
1 parent 4095501 commit 1dfd062

2 files changed

Lines changed: 24 additions & 10 deletions

File tree

fs/smb/server/oplock.c

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ static void free_lease(struct oplock_info *opinfo)
120120
kfree(lease);
121121
}
122122

123-
static void free_opinfo(struct oplock_info *opinfo)
123+
static void __free_opinfo(struct oplock_info *opinfo)
124124
{
125125
if (opinfo->is_lease)
126126
free_lease(opinfo);
@@ -129,6 +129,18 @@ static void free_opinfo(struct oplock_info *opinfo)
129129
kfree(opinfo);
130130
}
131131

132+
static void free_opinfo_rcu(struct rcu_head *rcu)
133+
{
134+
struct oplock_info *opinfo = container_of(rcu, struct oplock_info, rcu);
135+
136+
__free_opinfo(opinfo);
137+
}
138+
139+
static void free_opinfo(struct oplock_info *opinfo)
140+
{
141+
call_rcu(&opinfo->rcu, free_opinfo_rcu);
142+
}
143+
132144
struct oplock_info *opinfo_get(struct ksmbd_file *fp)
133145
{
134146
struct oplock_info *opinfo;
@@ -176,9 +188,9 @@ void opinfo_put(struct oplock_info *opinfo)
176188
free_opinfo(opinfo);
177189
}
178190

179-
static void opinfo_add(struct oplock_info *opinfo)
191+
static void opinfo_add(struct oplock_info *opinfo, struct ksmbd_file *fp)
180192
{
181-
struct ksmbd_inode *ci = opinfo->o_fp->f_ci;
193+
struct ksmbd_inode *ci = fp->f_ci;
182194

183195
down_write(&ci->m_lock);
184196
list_add(&opinfo->op_entry, &ci->m_op_list);
@@ -1277,20 +1289,21 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
12771289
set_oplock_level(opinfo, req_op_level, lctx);
12781290

12791291
out:
1280-
rcu_assign_pointer(fp->f_opinfo, opinfo);
1281-
opinfo->o_fp = fp;
1282-
12831292
opinfo_count_inc(fp);
1284-
opinfo_add(opinfo);
1293+
opinfo_add(opinfo, fp);
1294+
12851295
if (opinfo->is_lease) {
12861296
err = add_lease_global_list(opinfo);
12871297
if (err)
12881298
goto err_out;
12891299
}
12901300

1301+
rcu_assign_pointer(fp->f_opinfo, opinfo);
1302+
opinfo->o_fp = fp;
1303+
12911304
return 0;
12921305
err_out:
1293-
free_opinfo(opinfo);
1306+
__free_opinfo(opinfo);
12941307
return err;
12951308
}
12961309

fs/smb/server/oplock.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ struct oplock_info {
6969
struct lease *o_lease;
7070
struct list_head op_entry;
7171
struct list_head lease_entry;
72-
wait_queue_head_t oplock_q; /* Other server threads */
73-
wait_queue_head_t oplock_brk; /* oplock breaking wait */
72+
wait_queue_head_t oplock_q; /* Other server threads */
73+
wait_queue_head_t oplock_brk; /* oplock breaking wait */
74+
struct rcu_head rcu;
7475
};
7576

7677
struct lease_break_info {

0 commit comments

Comments
 (0)