Hi
For your reference, this is a KASAN log for one of the use-after-free possibilies.
==================================================================
BUG: KASAN: use-after-free in cifs_demultiplex_thread+0xc77/0x1200
Write of size 4 at addr ffff880032a29958 by task cifsd/1199
CPU: 0 PID: 1199 Comm: cifsd Tainted: G C 4.17.0+ #4
Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
Call Trace:
dump_stack+0x7b/0xb5
print_address_description+0x6f/0x280
kasan_report+0x25b/0x380
? cifs_demultiplex_thread+0xc77/0x1200
__asan_store4+0x7b/0x80
cifs_demultiplex_thread+0xc77/0x1200
? cifs_handle_standard+0x280/0x280
? compat_start_thread+0x60/0x60
? kasan_check_write+0x14/0x20
? finish_task_switch+0xf6/0x3b0
? __schedule+0x502/0xd80
? __sched_text_start+0x8/0x8
? __kthread_parkme+0xcb/0x100
? kthread_blkcg+0x70/0x70
kthread+0x180/0x1d0
? cifs_handle_standard+0x280/0x280
? kthread_create_worker_on_cpu+0xc0/0xc0
ret_from_fork+0x35/0x40
Allocated by task 2688:
save_stack+0x46/0xd0
kasan_kmalloc+0xad/0xe0
kasan_slab_alloc+0x11/0x20
kmem_cache_alloc+0xe8/0x200
mempool_alloc_slab+0x15/0x20
mempool_alloc+0x111/0x280
smb2_mid_entry_alloc+0x3e/0x180
smb2_setup_request+0x14f/0x2b0
cifs_send_recv+0x191/0x800
smb2_send_recv+0x1b3/0x300
SMB2_open+0x8ca/0x13a0
smb2_open_op_close+0x122/0x300
smb2_query_path_info+0x85/0x110
cifs_get_inode_info+0x911/0xf00
cifs_lookup+0x434/0x670
cifs_atomic_open+0xe2/0x640
path_openat+0x1484/0x1de0
do_filp_open+0x126/0x1c0
do_sys_open+0x17d/0x2b0
__x64_sys_openat+0x59/0x70
do_syscall_64+0x78/0x170
entry_SYSCALL_64_after_hwframe+0x44/0xa9
Freed by task 2688:
save_stack+0x46/0xd0
__kasan_slab_free+0x129/0x190
kasan_slab_free+0xe/0x10
kmem_cache_free+0x7c/0x210
mempool_free_slab+0x17/0x20
mempool_free+0x65/0x170
DeleteMidQEntry+0x71/0x90
cifs_delete_mid+0x7f/0x90
cifs_send_recv+0x47f/0x800
smb2_send_recv+0x1b3/0x300
SMB2_open+0x8ca/0x13a0
smb2_open_op_close+0x122/0x300
smb2_query_path_info+0x85/0x110
cifs_get_inode_info+0x911/0xf00
cifs_lookup+0x434/0x670
cifs_atomic_open+0xe2/0x640
path_openat+0x1484/0x1de0
do_filp_open+0x126/0x1c0
do_sys_open+0x17d/0x2b0
__x64_sys_openat+0x59/0x70
do_syscall_64+0x78/0x170
entry_SYSCALL_64_after_hwframe+0x44/0xa9
The buggy address belongs to the object at ffff880032a29900
which belongs to the cache cifs_mpx_ids of size 104
The buggy address is located 88 bytes inside of
104-byte region [ffff880032a29900, ffff880032a29968)
The buggy address belongs to the page:
page:ffffea0000ca8a40 count:1 mapcount:0 mapping:0000000000000000 index:0x0
flags: 0xfffffc0000100(slab)
raw: 000fffffc0000100 0000000000000000 0000000000000000 0000000180150015
raw: ffffea000059a600 0000000a0000000a ffff880011eb1340 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff880032a29800: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb
ffff880032a29880: fb fb fb fb fb fc fc fc fc fc fc fc fc fc fc fc
>ffff880032a29900: fb fb fb fb fb fb fb fb fb fb fb fb fb fc fc fc
^
ffff880032a29980: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb
ffff880032a29a00: fb fb fb fb fb fc fc fc fc fc fc fc fc fc fc fc
==================================================================
On 06/14/2018 10:38 AM, Lars Persson wrote:
With protocol version 2.0 mounts we have seen crashes with corrupt mid
entries. Either the server->pending_mid_q list becomes corrupt with a
cyclic reference in one element or a mid object fetched by the
demultiplexer thread becomes overwritten during use.
Code review identified a race between the demultiplexer thread and the
request issuing thread. The demultiplexer thread seems to be written
with the assumption that it is the sole user of the mid object until
it calls the mid callback which either wakes the issuer task or
deletes the mid.
This assumption is not true because the issuer task can be woken up
earlier by a signal. If the demultiplexer thread has proceeded as far
as setting the mid_state to MID_RESPONSE_RECEIVED then the issuer
thread will happily end up calling cifs_delete_mid while the
demultiplexer thread still is using the mid object.
Inserting a delay in the cifs demultiplexer thread widens the race
window and makes reproduction of the race very easy:
if (server->large_buf)
buf = server->bigbuf;
+ usleep_range(500, 4000);
server->lstrp = jiffies;
To resolve this I think the proper solution involves putting a
reference count on the mid object. This patch makes sure that the
demultiplexer thread holds a reference until it has finished
processing the transaction.
Signed-off-by: Lars Persson <larper@xxxxxxxx>
---
fs/cifs/cifsglob.h | 1 +
fs/cifs/cifsproto.h | 1 +
fs/cifs/connect.c | 7 ++++++-
fs/cifs/smb1ops.c | 1 +
fs/cifs/smb2ops.c | 1 +
fs/cifs/smb2transport.c | 1 +
fs/cifs/transport.c | 18 +++++++++++++++++-
7 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index cb950a5fa078..c7ee09d9a236 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -1362,6 +1362,7 @@ typedef int (mid_handle_t)(struct TCP_Server_Info *server,
/* one of these for every pending CIFS request to the server */
struct mid_q_entry {
struct list_head qhead; /* mids waiting on reply from this server */
+ struct kref refcount;
struct TCP_Server_Info *server; /* server corresponding to this mid */
__u64 mid; /* multiplex id */
__u32 pid; /* process id */
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index 365a414a75e9..c4e5c69810f9 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -76,6 +76,7 @@ extern struct mid_q_entry *AllocMidQEntry(const struct smb_hdr *smb_buffer,
struct TCP_Server_Info *server);
extern void DeleteMidQEntry(struct mid_q_entry *midEntry);
extern void cifs_delete_mid(struct mid_q_entry *mid);
+extern void cifs_mid_q_entry_release(struct mid_q_entry *midEntry);
extern void cifs_wake_up_task(struct mid_q_entry *mid);
extern int cifs_handle_standard(struct TCP_Server_Info *server,
struct mid_q_entry *mid);
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 7a10a5d0731f..90cedf6b3228 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -920,8 +920,11 @@ cifs_demultiplex_thread(void *p)
length = mid_entry->receive(server, mid_entry);
}
- if (length < 0)
+ if (length < 0) {
+ if (mid_entry)
+ cifs_mid_q_entry_release(mid_entry);
continue;
+ }
if (server->large_buf)
buf = server->bigbuf;
@@ -938,6 +941,8 @@ cifs_demultiplex_thread(void *p)
if (!mid_entry->multiRsp || mid_entry->multiEnd)
mid_entry->callback(mid_entry);
+
+ cifs_mid_q_entry_release(mid_entry);
} else if (server->ops->is_oplock_break &&
server->ops->is_oplock_break(buf, server)) {
cifs_dbg(FYI, "Received oplock break\n");
diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
index aff8ce8ba34d..646dcd149de1 100644
--- a/fs/cifs/smb1ops.c
+++ b/fs/cifs/smb1ops.c
@@ -107,6 +107,7 @@ cifs_find_mid(struct TCP_Server_Info *server, char *buffer)
if (compare_mid(mid->mid, buf) &&
mid->mid_state == MID_REQUEST_SUBMITTED &&
le16_to_cpu(mid->command) == buf->Command) {
+ kref_get(&mid->refcount);
spin_unlock(&GlobalMid_Lock);
return mid;
}
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index 9c6d95ffca97..ba0bc31786d1 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -203,6 +203,7 @@ smb2_find_mid(struct TCP_Server_Info *server, char *buf)
if ((mid->mid == wire_mid) &&
(mid->mid_state == MID_REQUEST_SUBMITTED) &&
(mid->command == shdr->Command)) {
+ kref_get(&mid->refcount);
spin_unlock(&GlobalMid_Lock);
return mid;
}
diff --git a/fs/cifs/smb2transport.c b/fs/cifs/smb2transport.c
index 8806f3f76c1d..97f24d82ae6b 100644
--- a/fs/cifs/smb2transport.c
+++ b/fs/cifs/smb2transport.c
@@ -548,6 +548,7 @@ smb2_mid_entry_alloc(const struct smb2_sync_hdr *shdr,
temp = mempool_alloc(cifs_mid_poolp, GFP_NOFS);
memset(temp, 0, sizeof(struct mid_q_entry));
+ kref_init(&temp->refcount);
temp->mid = le64_to_cpu(shdr->MessageId);
temp->pid = current->pid;
temp->command = shdr->Command; /* Always LE */
diff --git a/fs/cifs/transport.c b/fs/cifs/transport.c
index 927226a2122f..60faf2fcec7f 100644
--- a/fs/cifs/transport.c
+++ b/fs/cifs/transport.c
@@ -61,6 +61,7 @@ AllocMidQEntry(const struct smb_hdr *smb_buffer, struct TCP_Server_Info *server)
temp = mempool_alloc(cifs_mid_poolp, GFP_NOFS);
memset(temp, 0, sizeof(struct mid_q_entry));
+ kref_init(&temp->refcount);
temp->mid = get_mid(smb_buffer);
temp->pid = current->pid;
temp->command = cpu_to_le16(smb_buffer->Command);
@@ -82,6 +83,21 @@ AllocMidQEntry(const struct smb_hdr *smb_buffer, struct TCP_Server_Info *server)
return temp;
}
+static void _cifs_mid_q_entry_release(struct kref *refcount)
+{
+ struct mid_q_entry *mid = container_of(refcount, struct mid_q_entry,
+ refcount);
+
+ mempool_free(mid, cifs_mid_poolp);
+}
+
+void cifs_mid_q_entry_release(struct mid_q_entry *midEntry)
+{
+ spin_lock(&GlobalMid_Lock);
+ kref_put(&midEntry->refcount, _cifs_mid_q_entry_release);
+ spin_unlock(&GlobalMid_Lock);
+}
+
void
DeleteMidQEntry(struct mid_q_entry *midEntry)
{
@@ -110,7 +126,7 @@ DeleteMidQEntry(struct mid_q_entry *midEntry)
}
}
#endif
- mempool_free(midEntry, cifs_mid_poolp);
+ cifs_mid_q_entry_release(midEntry);
}
void
--
To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html