|
| 1 | +smb: client: fix use-after-free in cifs_oplock_break |
| 2 | + |
| 3 | +jira LE-4375 |
| 4 | +cve CVE-2025-38527 |
| 5 | +Rebuild_History Non-Buildable kernel-4.18.0-553.78.1.el8_10 |
| 6 | +commit-author Wang Zhaolong <wangzhaolong@huaweicloud.com> |
| 7 | +commit 705c79101ccf9edea5a00d761491a03ced314210 |
| 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-4.18.0-553.78.1.el8_10/705c7910.failed |
| 11 | + |
| 12 | +A race condition can occur in cifs_oplock_break() leading to a |
| 13 | +use-after-free of the cinode structure when unmounting: |
| 14 | + |
| 15 | + cifs_oplock_break() |
| 16 | + _cifsFileInfo_put(cfile) |
| 17 | + cifsFileInfo_put_final() |
| 18 | + cifs_sb_deactive() |
| 19 | + [last ref, start releasing sb] |
| 20 | + kill_sb() |
| 21 | + kill_anon_super() |
| 22 | + generic_shutdown_super() |
| 23 | + evict_inodes() |
| 24 | + dispose_list() |
| 25 | + evict() |
| 26 | + destroy_inode() |
| 27 | + call_rcu(&inode->i_rcu, i_callback) |
| 28 | + spin_lock(&cinode->open_file_lock) <- OK |
| 29 | + [later] i_callback() |
| 30 | + cifs_free_inode() |
| 31 | + kmem_cache_free(cinode) |
| 32 | + spin_unlock(&cinode->open_file_lock) <- UAF |
| 33 | + cifs_done_oplock_break(cinode) <- UAF |
| 34 | + |
| 35 | +The issue occurs when umount has already released its reference to the |
| 36 | +superblock. When _cifsFileInfo_put() calls cifs_sb_deactive(), this |
| 37 | +releases the last reference, triggering the immediate cleanup of all |
| 38 | +inodes under RCU. However, cifs_oplock_break() continues to access the |
| 39 | +cinode after this point, resulting in use-after-free. |
| 40 | + |
| 41 | +Fix this by holding an extra reference to the superblock during the |
| 42 | +entire oplock break operation. This ensures that the superblock and |
| 43 | +its inodes remain valid until the oplock break completes. |
| 44 | + |
| 45 | +Link: https://bugzilla.kernel.org/show_bug.cgi?id=220309 |
| 46 | +Fixes: b98749cac4a6 ("CIFS: keep FileInfo handle live during oplock break") |
| 47 | + Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org> |
| 48 | + Signed-off-by: Wang Zhaolong <wangzhaolong@huaweicloud.com> |
| 49 | + Signed-off-by: Steve French <stfrench@microsoft.com> |
| 50 | +(cherry picked from commit 705c79101ccf9edea5a00d761491a03ced314210) |
| 51 | + Signed-off-by: Jonathan Maple <jmaple@ciq.com> |
| 52 | + |
| 53 | +# Conflicts: |
| 54 | +# fs/cifs/file.c |
| 55 | +diff --cc fs/cifs/file.c |
| 56 | +index 6aaac9bc59dc,1421bde045c2..000000000000 |
| 57 | +--- a/fs/cifs/file.c |
| 58 | ++++ b/fs/cifs/file.c |
| 59 | +@@@ -4767,12 -3088,23 +4767,23 @@@ void cifs_oplock_break(struct work_stru |
| 60 | + struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo, |
| 61 | + oplock_break); |
| 62 | + struct inode *inode = d_inode(cfile->dentry); |
| 63 | +++<<<<<<< HEAD:fs/cifs/file.c |
| 64 | +++======= |
| 65 | ++ struct super_block *sb = inode->i_sb; |
| 66 | ++ struct cifs_sb_info *cifs_sb = CIFS_SB(sb); |
| 67 | +++>>>>>>> 705c79101ccf (smb: client: fix use-after-free in cifs_oplock_break):fs/smb/client/file.c |
| 68 | + struct cifsInodeInfo *cinode = CIFS_I(inode); |
| 69 | + - struct cifs_tcon *tcon; |
| 70 | + - struct TCP_Server_Info *server; |
| 71 | + - struct tcon_link *tlink; |
| 72 | + + struct cifs_tcon *tcon = tlink_tcon(cfile->tlink); |
| 73 | + + struct TCP_Server_Info *server = tcon->ses->server; |
| 74 | + int rc = 0; |
| 75 | + - bool purge_cache = false, oplock_break_cancelled; |
| 76 | + - __u64 persistent_fid, volatile_fid; |
| 77 | + - __u16 net_fid; |
| 78 | + + bool purge_cache = false; |
| 79 | + |
| 80 | ++ /* |
| 81 | ++ * Hold a reference to the superblock to prevent it and its inodes from |
| 82 | ++ * being freed while we are accessing cinode. Otherwise, _cifsFileInfo_put() |
| 83 | ++ * may release the last reference to the sb and trigger inode eviction. |
| 84 | ++ */ |
| 85 | ++ cifs_sb_active(sb); |
| 86 | + wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS, |
| 87 | + TASK_UNINTERRUPTIBLE); |
| 88 | + |
| 89 | +@@@ -4808,39 -3146,40 +4819,40 @@@ |
| 90 | + |
| 91 | + oplock_break_ack: |
| 92 | + /* |
| 93 | + - * When oplock break is received and there are no active |
| 94 | + - * file handles but cached, then schedule deferred close immediately. |
| 95 | + - * So, new open will not use cached handle. |
| 96 | + - */ |
| 97 | + - |
| 98 | + - if (!CIFS_CACHE_HANDLE(cinode) && !list_empty(&cinode->deferred_closes)) |
| 99 | + - cifs_close_deferred_file(cinode); |
| 100 | + - |
| 101 | + - persistent_fid = cfile->fid.persistent_fid; |
| 102 | + - volatile_fid = cfile->fid.volatile_fid; |
| 103 | + - net_fid = cfile->fid.netfid; |
| 104 | + - oplock_break_cancelled = cfile->oplock_break_cancelled; |
| 105 | + - |
| 106 | + - _cifsFileInfo_put(cfile, false /* do not wait for ourself */, false); |
| 107 | + - /* |
| 108 | + - * MS-SMB2 3.2.5.19.1 and 3.2.5.19.2 (and MS-CIFS 3.2.5.42) do not require |
| 109 | + - * an acknowledgment to be sent when the file has already been closed. |
| 110 | + + * releasing stale oplock after recent reconnect of smb session using |
| 111 | + + * a now incorrect file handle is not a data integrity issue but do |
| 112 | + + * not bother sending an oplock release if session to server still is |
| 113 | + + * disconnected since oplock already released by the server |
| 114 | + */ |
| 115 | + - spin_lock(&cinode->open_file_lock); |
| 116 | + - /* check list empty since can race with kill_sb calling tree disconnect */ |
| 117 | + - if (!oplock_break_cancelled && !list_empty(&cinode->openFileList)) { |
| 118 | + - spin_unlock(&cinode->open_file_lock); |
| 119 | + - rc = server->ops->oplock_response(tcon, persistent_fid, |
| 120 | + - volatile_fid, net_fid, cinode); |
| 121 | + + if (!cfile->oplock_break_cancelled) { |
| 122 | + + rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid, |
| 123 | + + cinode); |
| 124 | + cifs_dbg(FYI, "Oplock release rc = %d\n", rc); |
| 125 | + - } else |
| 126 | + - spin_unlock(&cinode->open_file_lock); |
| 127 | + - |
| 128 | + - cifs_put_tlink(tlink); |
| 129 | + -out: |
| 130 | + + } |
| 131 | + + _cifsFileInfo_put(cfile, false /* do not wait for ourself */, false); |
| 132 | + cifs_done_oplock_break(cinode); |
| 133 | ++ cifs_sb_deactive(sb); |
| 134 | + } |
| 135 | + |
| 136 | + +/* |
| 137 | + + * The presence of cifs_direct_io() in the address space ops vector |
| 138 | + + * allowes open() O_DIRECT flags which would have failed otherwise. |
| 139 | + + * |
| 140 | + + * In the non-cached mode (mount with cache=none), we shunt off direct read and write requests |
| 141 | + + * so this method should never be called. |
| 142 | + + * |
| 143 | + + * Direct IO is not yet supported in the cached mode. |
| 144 | + + */ |
| 145 | + +static ssize_t |
| 146 | + +cifs_direct_io(struct kiocb *iocb, struct iov_iter *iter) |
| 147 | + +{ |
| 148 | + + /* |
| 149 | + + * FIXME |
| 150 | + + * Eventually need to support direct IO for non forcedirectio mounts |
| 151 | + + */ |
| 152 | + + return -EINVAL; |
| 153 | + +} |
| 154 | + + |
| 155 | + static int cifs_swap_activate(struct swap_info_struct *sis, |
| 156 | + struct file *swap_file, sector_t *span) |
| 157 | + { |
| 158 | +* Unmerged path fs/cifs/file.c |
0 commit comments