3398 words
17 minutes
CVE-2025-37778/CVE-2025-37899

Intro#

TOOR 팀 활동을 하며 분석하기보단 알게된 리눅스 커널 SMB 서버 구현체에서 발생하는 취약점입니다! 커널에서 SMB 서버를 구현한다는 것도 처음 알았네요 허허

최근에는 대부분의 시간을 AI 공부에 쏟는 거 같네요!

쳇바퀴같이 반복되는 요즘 삶에 AI까지 끼워넣어버린 내 상태

AI 열풍이 불어도 관련 공부가 손에 잘 안잡혔었는데, 이번 기회에 AI.. 특히 에이전트 개발에 대해서 깊게 공부해보기로 했씁니다.

CVE-2025-37899은 2025년 4월경 커밋이 올라오고 2025년 5월 20일에 취약점 정보가 공개된 리눅스 커널의 UAF 취약점입니다. 현재 글 작성 시점 비교적 최근에 발견된 취약점이라 그런지, 이에 대한 정보는 아직 상세히 나와있지 않습니다.

이번에 알아볼 취약점은 OpenAI의 AI 모델 중 하나인 o3를 통해 발견되었다고 합니다.

본 글은 선행 연구를 진행하신 다른 연구원분들의 글들을 읽고 제 나름 분석을 진행하며 취약점을 공부하며 이해하고 정리해본 결과로 작성하게된 글입니다. 나름의 분석을 해봤지만 맞지 않는 부분이 있을 수 있으며, 만약 이를 발견하셨을 시 피드백해주시면 적극 반영하도록 하겠습니다. 취약점 및 PoC 분석에 많은 도움이된 자료들은 다음과 같습니다.

Vuln#

Background#

먼저 이번의 취약점이 발생한 구간을 이해하기 위한 기초 지식에 대한 내용을 조금 공부해보도록 합시다.

SMB3 Kernel Server#

지금부터 알아볼 리눅스 커널 속 SMB3 서버에 대한 아키텍처 구조입니다.

ksmbd#

리눅스 커널은 놀랍고도 신기하게도 SMB3 서버 구현체를 커널 내부적으로 가지고 있습니다. 즉, 커널에서 SMB3 서버 기능을 커널 모듈 형태로 제공하게됩니다.

주된 목적은 최적화로써 소형 장치에서도 사용하기 쉽게 만들려는 노력으로 보입니다. 유명한 서버 프로그램 중 하나인 Samba를 대체하기 위한 것이 아닌 리눅스에서의 최적화를 목적으로 한다고 합니다.

이때 지금 언급하는 ksmbd는 서버 역할을 하는 리눅스 커널 모듈이며, 성능과 관련된 파일 작업등을 처리해주는 역할을 합니다.

ksmbd.mountd#

유저 영역에 존재하는 데몬으로 netlink를 통해 ksmbd와 연결되어 요청을 수행하게됩니다. 다음과 같은 역할 역시 수행합니다.

  • 사용자 계정 및 비밀번호 관리 및 커널 데몬으로 전달
  • 커널 데몬으로 공유 정보 파라미터 전달
  • RPC 호출 처리

CVE-2025-37778#

CVE-2025-37899를 찾아낸 제보자는 당시 새로 출시된 o3의 능력을 벤치마킹 해보기 위해 본인이 직접 찾아낸 CVE-2025-37778를 활용하게 됩니다. 이에 대해서 알아보도록 합시다.

CVE-2025-37778은 UAF 버그로 이 취약점을 통해 알아볼 점은 어떠한 문제가 커널 삼바 서버 코드내에 존재하는지와 어떤 식으로 이를 트리거 시켜야하는지에 대해서 정도입니다.

RCA#

In the Linux kernel, the following vulnerability has been resolved: ksmbd: Fix dangling pointer in krb_authenticate krb_authenticate frees sess->user and does not set the pointer to NULL. It calls ksmbd_krb5_authenticate to reinitialise sess->user but that function may return without doing so. If that happens then smb2_sess_setup, which calls krb_authenticate, will be accessing free'd memory when it later uses sess->user.

Description에 따르면 krb_authenticate에서 해제된 포인터를 가지고 있는 멤버 sess->user에 대해서 적절하게 NULL 설정이 이루어지지 않는다고 하고 있습니다. 그리고 sess->user를 재설정할 책임이 있는 ksmbd_krb5_authenticate 역시 sess->user에 대한 재설정 처리를 하지 않게 됨으로써 UAF가 발생한다고 합니다.

sess->stateSMB2_SESSION_VALID 값을 가지는 경우는 다음과 같은 smb2_sess_setup에서 일어납니다.

int smb2_sess_setup(struct ksmbd_work *work)
{
...
if (!ksmbd_conn_need_reconnect(conn)) {
ksmbd_conn_set_good(conn);
sess->state = SMB2_SESSION_VALID;
}
...
if (!ksmbd_conn_need_reconnect(conn)) {
ksmbd_conn_set_good(conn);
sess->state = SMB2_SESSION_VALID;
}
...
}

앞서 언급했듯, 취약점은 krb5_authenticate에서 발생합니다. sess->stateSMB2_SESSION_VALID값을 가지게 될 경우 ksbmd_free_user를 통해 sess->user를 해제합니다.

그리고 후속조치로 호출되는 ksmbd_krb5_authenticate에서 세션 초기화가 정상적으로 진행된다고 보고있습니다.

#ifdef CONFIG_SMB_SERVER_KERBEROS5
static int krb5_authenticate(struct ksmbd_work *work,
struct smb2_sess_setup_req *req,
struct smb2_sess_setup_rsp *rsp)
{
struct ksmbd_conn *conn = work->conn;
struct ksmbd_session *sess = work->sess;
char *in_blob, *out_blob;
struct channel *chann = NULL;
u64 prev_sess_id;
int in_len, out_len;
int retval;
...
if (sess->state == SMB2_SESSION_VALID)
ksmbd_free_user(sess->user);
retval = ksmbd_krb5_authenticate(sess, in_blob, in_len,
out_blob, &out_len);
if (retval) {
ksmbd_debug(SMB, "krb5 authentication failed\n");
return -EINVAL;
}
...
return 0;
}

ksmbd_krb5_authenticate를 통해 sess->user가 유효한 값으로 초기화 되거나 또는 에러 값 반환으로 인해 sess->user가 다른 구간에서 사용되지 않아야 하지만, 초기화가 되지 않은 상태에서 다른 구간에서 사용하는 경우가 발생합니다. 즉, 해제는 했음에도 sess->user가 다른 곳에서 참조되는 상황이 발생합니다. (Use-After-Free)

ksmbd_krb5_authenticate를 한번 들여다볼까요?

#ifdef CONFIG_SMB_SERVER_KERBEROS5
int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
int in_len, char *out_blob, int *out_len)
{
struct ksmbd_spnego_authen_response *resp;
struct ksmbd_login_response_ext *resp_ext = NULL;
struct ksmbd_user *user = NULL;
int retval;
resp = ksmbd_ipc_spnego_authen_request(in_blob, in_len);
if (!resp) {
ksmbd_debug(AUTH, "SPNEGO_AUTHEN_REQUEST failure\n");
return -EINVAL;
}
if (!(resp->login_response.status & KSMBD_USER_FLAG_OK)) {
ksmbd_debug(AUTH, "krb5 authentication failure\n");
retval = -EPERM;
goto out;
}
if (*out_len <= resp->spnego_blob_len) {
ksmbd_debug(AUTH, "buf len %d, but blob len %d\n",
*out_len, resp->spnego_blob_len);
retval = -EINVAL;
goto out;
}
if (resp->session_key_len > sizeof(sess->sess_key)) {
ksmbd_debug(AUTH, "session key is too long\n");
retval = -EINVAL;
goto out;
}
if (resp->login_response.status & KSMBD_USER_FLAG_EXTENSION)
resp_ext = ksmbd_ipc_login_request_ext(resp->login_response.account);
user = ksmbd_alloc_user(&resp->login_response, resp_ext);
if (!user) {
ksmbd_debug(AUTH, "login failure\n");
retval = -ENOMEM;
goto out;
}
sess->user = user;
memcpy(sess->sess_key, resp->payload, resp->session_key_len);
memcpy(out_blob, resp->payload + resp->session_key_len,
resp->spnego_blob_len);
*out_len = resp->spnego_blob_len;
retval = 0;
out:
kvfree(resp);
return retval;
}

코드에서 볼 수 있듯, sess->user는 많은 조건문을 거치고나서 값 설정이 진행이됩니다. 중간 조건문을 트리거 시킬 수 있다면, ksmbd_krb5_authenticate에서 값을 재설정하는 것을 막을 수 있겠죠. 이로인해 sess->user는 해제된 영역을 여전히 가리키고 있게됩니다. 그리고 최종적으로 krb5_authenticate가 오류로 인해 -EINVAL 값을 반환했을 때, 이전에 free 처리된 sess->user는 재사용되지 않아야하지만, 특정 영역에서 재사용됨을 언급하고있습니다.

뒤에 서술된 벤치마킹의 o3의 보고서에서 이에 대한 활용 방안이 자세히 제시되어있습니다.

What CVE-2025-37778 Teaches Us and How to Use It in Benchmarking#

위에서 알아본 취약점을 벤치마킹에 활용하기 위해서 따져봐야할 것들에 대해서 생각해봅시다.

위 취약점에서 볼 수 있듯, 다음과 같은 여부를 따져봐야겠죠?

  • 앞서 알아본 ksmbd_free_user의 트리거
  • sess->user를 다시 덮어쓸 수 없게 하는 경로의 트리거
  • 위 두 과정을 통해 아직 초기화되지 않은 sess->user에 접근할 코드 구간의 존재 여부

제보자는 o3의 벤치마킹을 위해 위 취약점을 사용했다했습니다. 제보자는 다음과 같은 정리를 합니다.

  • 위 취약점을 이해하기 위해서는 연결 처리, 세션 설정과 관련된 코드만 알면됨
  • 취약점 트리거까지 호출되는 함수를 ksmbd에서 최소한으로 읽는다 했을 때의 분량은 약 3,300줄

이와 같은 환경에서 LLM에 어떻게 명확한 코드를 제시하고, 성능에 대해서 고민하게됩니다.

  • LLM에는 성능의 문제로 인해 모든 코드를 넘겨주는 것은 비효율적임
  • 자동화된 LLM 취약점 탐지 시스템에서 어떤 함수를 분석할지 분명히할 수 없다면, 의미가 없음

이에 대해서는 다음과 같은 결론을 내리게됩니다.

  • SMB에 대한 명령 핸들러를 개별적으로 확장시킴. 즉, 세션 설정에 대한 명령 핸들러가 존재한다면, 해당 핸들러에서 호출하는 모든 코드를 포함시킴
  • 위 과정에서 함수의 호출 깊이는 3으로 제한, 이는 CVE-2025-37778를 이해하기 위해 읽어야할 함수 호출 최대 깊이

세션 설정에 대한 핸들러외에도 다음과 같은 함수까지 포함시켜 LLM의 정확성을 높였다고 합니다.

  • 네트워크에서 데이터를 읽어오는 함수
  • 들어오는 요청을 파싱하는 함수
  • 실행할 명령 핸들러를 선택하는 함수
  • 실행이 끝난 뒤 연결을 종료하는 함수

결과적으로 벤치마킹에 사용될 3,300줄(약 27,000 토큰)을 가진 프롬프트(session_setup_context)가 완성됩니다. 이는 코드에 해당하는 프롬프트며 다음과 같은 프롬프트 파일들이 추가적으로 사용됩니다.

  • system_prompt_uafs.prompt : False positive에 대한 방지 유도, 리눅스 커널 코드에서 UAF 취약점을 유발하는 부분 탐지 요청
  • ksmbd_explainer.prompt : ksmbd 아키텍처에 대한 설명
  • session_setup_context_explainer.prompt : 주어진 ksmbd 코드 컨텍스트(session_setup_context.prompt)에 대한 설명(오디팅할 SMB 명령어들을 찾을 수 있는 위치, 함수의 깊이등)
  • audit_request.prompt : 오디팅 요청

Benchmark results#

위 과정에서 CVE-2025-37778를 찾을 수 있는가에 대한 결과는 다음과 같이 나왔다고합니다. (100번의 실행동안의 결과)

모델성공 횟수
OpenAI o38
Claude Sonnet 3.73
Claude Sonnet 3.5x

session setup 핸들러에 대한 밴치마킹 이후, 제보자는 모든 명령 핸들러에 대한 취약점을 찾고자합니다. 그리고 결과적으로 CVE-2025-37899를 발견할 수 있었습니다. 이에 대해서 알아봅시다.

CVE-2025-37899#

필자는 smb2_session_setup 핸들러외에 존재하는 모든 핸들러에서 취약점 탐지 테스팅을 진행합니다. 다음과 같이 smb2_session_setup외에 다양한 명령어 핸들러들이 존재합니다.

https://elixir.bootlin.com/linux/v6.14/source/fs/smb/server/smb2ops.c#L171

static struct smb_version_cmds smb2_0_server_cmds[NUMBER_OF_SMB2_COMMANDS] = {
[SMB2_NEGOTIATE_HE] = { .proc = smb2_negotiate_request, },
[SMB2_SESSION_SETUP_HE] = { .proc = smb2_sess_setup, },
[SMB2_TREE_CONNECT_HE] = { .proc = smb2_tree_connect,},
[SMB2_TREE_DISCONNECT_HE] = { .proc = smb2_tree_disconnect,},
[SMB2_LOGOFF_HE] = { .proc = smb2_session_logoff,},
[SMB2_CREATE_HE] = { .proc = smb2_open},
[SMB2_QUERY_INFO_HE] = { .proc = smb2_query_info},
[SMB2_QUERY_DIRECTORY_HE] = { .proc = smb2_query_dir},
[SMB2_CLOSE_HE] = { .proc = smb2_close},
[SMB2_ECHO_HE] = { .proc = smb2_echo},
[SMB2_SET_INFO_HE] = { .proc = smb2_set_info},
[SMB2_READ_HE] = { .proc = smb2_read},
[SMB2_WRITE_HE] = { .proc = smb2_write},
[SMB2_FLUSH_HE] = { .proc = smb2_flush},
[SMB2_CANCEL_HE] = { .proc = smb2_cancel},
[SMB2_LOCK_HE] = { .proc = smb2_lock},
[SMB2_IOCTL_HE] = { .proc = smb2_ioctl},
[SMB2_OPLOCK_BREAK_HE] = { .proc = smb2_oplock_break},
[SMB2_CHANGE_NOTIFY_HE] = { .proc = smb2_notify},
};

결과적으로 이 핸들러 + 알파에 대한 100k 토큰을 갖는 요청을 만들고 o3에서 100번의 실행 중 단 한번에서 CVE-2025-37899를 발견해냅니다.

새로운 이 취약점은 SMB2_LOGOFF_HE 커맨드를 처리하는 smb2_session_logoff 함수에서 발생하게됩니다.

RCA#

CVE-2025-37899는 동기화 장치가 없음에 따른 레이스 컨디션으로 발생하는 Use-After-Free입니다.

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/fs/smb/server/smb2pdu.c?id=e86e9134e1d1c90a960dd57f59ce574d27b9a124#n2252

/**
* smb2_session_logoff() - handler for session log off request
* @work: smb work containing request buffer
*
* Return: 0
*/
int smb2_session_logoff(struct ksmbd_work *work)
{
if (sess->user) {
ksmbd_free_user(sess->user);
sess->user = NULL;
}
...
}

해당 취약점은 하나의 세션에 두 개의 스레드가 동작할 때, 하나의 스레드 A에서 sess->user에 대한 역참조를 진행하기 전, smb2_session_logoff를 사용하는 스레드 B에 의해서 sess->userksmbd_free_user(sess->user);에 의해 해제될 경우 이를 동기화 하는 장치가 없기 때문에 결과적으로 해제된 메모리를 역참조하는 경우입니다.(UAF)

위 코드에 등장하는 sess->user = NULL;은 의미가 없습니다. 레이스 컨디션 원리상 ksmbd_free_user(sess->user);가 호출된 시점에 해당 메모리에 접근을 할 수 있다면 충분히 악용할 수 있기 때문입니다.

Patch#

패치에서는 smb2_session_logoff 에서 sess->user를 해제하고 NULL을 설정하는 코드 자체를 삭제합니다.

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index acc05657cfc72a..46aa082457424b 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2249,10 +2249,6 @@ int smb2_session_logoff(struct ksmbd_work *work)
sess->state = SMB2_SESSION_EXPIRED;
up_write(&conn->session_lock);
- if (sess->user) {
- ksmbd_free_user(sess->user);
- sess->user = NULL;
- }
ksmbd_all_conn_set_status(sess_id, KSMBD_SESS_NEED_SETUP);
rsp->StructureSize = cpu_to_le16(4);

Conclusion#

이번 글에서 알아본 취약점을 발굴하는 과정은 현재의 LLM의 수준이 인간 오디터에 훨씬 가까워졌음을 시사합니다. (라고 원 글에서도 언급합니다.)

또한, 이렇게 발전한 AI 관련 기술들은 적절하게 활용된다면 취약점 연구에서 성과 향상에 중요한 재료가 될거라고 제보자는 언급하고 있습니다. 실제로 이런 글을 접해보니 모델 발전이 꾸준히 잘 이루어지고 있는 듯 하고, 활용 가치도 커 보입니다. 안쓰면 오히려 손해인 느낌?

앞으로의 할일은 목전에 둔 AI 에이전트 개발에 매진하는 일이겠네요. 팀 프로젝트인 만큼 기대가 큽니다. 재밌겠네요 🙂.

LLM으로 신세계의 신이된다.

AI로 우주정복을 하고 돌아오도록 하겠습니다. 그럼 20000.

Mitigation#

  • 해당 취약점에 대한 커널 업그레이드를 진행하세용 ^~^

References#