Skip to content

Commit 787e4fa

Browse files
[Medium] Patch erlang for CVE-2025-48039 (microsoft#14783)
1 parent 826a19c commit 787e4fa

File tree

2 files changed

+235
-1
lines changed

2 files changed

+235
-1
lines changed

SPECS/erlang/CVE-2025-48039.patch

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
From 043ee3c943e2977c1acdd740ad13992fd60b6bf0 Mon Sep 17 00:00:00 2001
2+
From: Jakub Witczak <[email protected]>
3+
Date: Fri, 11 Jul 2025 13:59:41 +0200
4+
Subject: [PATCH] ssh: ssh_sftpd verify path size for client data
5+
6+
- reject max_path exceeding the 4096 limit or according to other option value
7+
8+
Modified to apply to Azure Linux
9+
Modified by: Akhila Guruju <[email protected]>
10+
Date: Fri, 3 Oct 2025 11:07:55 +0000
11+
12+
Upstream Patch Reference: https://github.com/erlang/otp/commit/043ee3c943e2977c1acdd740ad13992fd60b6bf0.patch
13+
---
14+
lib/ssh/doc/src/ssh_sftpd.xml | 8 ++++
15+
lib/ssh/src/ssh_sftpd.erl | 28 ++++++++++++
16+
lib/ssh/test/ssh_sftpd_SUITE.erl | 78 ++++++++++++++++++++------------
17+
3 files changed, 85 insertions(+), 29 deletions(-)
18+
19+
diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml
20+
index 03e8dad..cbe015f 100644
21+
--- a/lib/ssh/doc/src/ssh_sftpd.xml
22+
+++ b/lib/ssh/doc/src/ssh_sftpd.xml
23+
@@ -65,6 +65,14 @@
24+
If supplied, the number of filenames returned to the SFTP client per <c>READDIR</c>
25+
request is limited to at most the given value.</p>
26+
</item>
27+
+ <tag><c>max_path</c></tag>
28+
+ <item>
29+
+ <p>The default value is <c>4096</c>. Positive integer
30+
+ value represents the maximum path length which cannot be
31+
+ exceeded in data provided by the SFTP client. (Note:
32+
+ limitations might be also enforced by underlying operating
33+
+ system)</p>
34+
+ </item>
35+
<tag><c>max_handles</c></tag>
36+
<item>
37+
<p>The default value is <c>1000</c>. Positive integer value represents the maximum number of file handles allowed for a connection.</p>
38+
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
39+
index 0c64178..d02ece3 100644
40+
--- a/lib/ssh/src/ssh_sftpd.erl
41+
+++ b/lib/ssh/src/ssh_sftpd.erl
42+
@@ -52,6 +52,7 @@
43+
file_handler, % atom() - callback module
44+
file_state, % state for the file callback module
45+
max_files, % integer >= 0 max no files sent during READDIR
46+
+ max_path, % integer > 0 - max length of path
47+
max_handles, % integer > 0 - max number of file handles
48+
options, % from the subsystem declaration
49+
handles % list of open handles
50+
@@ -66,6 +67,7 @@
51+
Options :: [ {cwd, string()} |
52+
{file_handler, CbMod | {CbMod, FileState}} |
53+
{max_files, integer()} |
54+
+ {max_path, integer()} |
55+
{max_handles, integer()} |
56+
{root, string()} |
57+
{sftpd_vsn, integer()}
58+
@@ -117,11 +119,13 @@ init(Options) ->
59+
{Root0, State0}
60+
end,
61+
MaxLength = proplists:get_value(max_files, Options, 0),
62+
+ MaxPath = proplists:get_value(max_path, Options, 4096),
63+
MaxHandles = proplists:get_value(max_handles, Options, 1000),
64+
Vsn = proplists:get_value(sftpd_vsn, Options, 5),
65+
{ok, State#state{cwd = CWD,
66+
root = Root,
67+
max_files = MaxLength,
68+
+ max_path = MaxPath,
69+
max_handles = MaxHandles,
70+
options = Options,
71+
handles = [], pending = <<>>,
72+
@@ -239,6 +243,30 @@ handle_op(Request, ReqId, <<?UINT32(HLen), _/binary>>, State = #state{xf = XF})
73+
HLen > 256 ->
74+
ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_INVALID_HANDLE, "Invalid handle"),
75+
State;
76+
+handle_op(Request, ReqId, <<?UINT32(PLen), _/binary>>,
77+
+ State = #state{max_path = MaxPath, xf = XF})
78+
+ when (Request == ?SSH_FXP_LSTAT orelse
79+
+ Request == ?SSH_FXP_MKDIR orelse
80+
+ Request == ?SSH_FXP_OPEN orelse
81+
+ Request == ?SSH_FXP_OPENDIR orelse
82+
+ Request == ?SSH_FXP_READLINK orelse
83+
+ Request == ?SSH_FXP_REALPATH orelse
84+
+ Request == ?SSH_FXP_REMOVE orelse
85+
+ Request == ?SSH_FXP_RMDIR orelse
86+
+ Request == ?SSH_FXP_SETSTAT orelse
87+
+ Request == ?SSH_FXP_STAT),
88+
+ PLen > MaxPath ->
89+
+ ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_NO_SUCH_PATH,
90+
+ "No such path"),
91+
+ State;
92+
+handle_op(Request, ReqId, <<?UINT32(PLen), _:PLen/binary, ?UINT32(PLen2), _/binary>>,
93+
+ State = #state{max_path = MaxPath, xf = XF})
94+
+ when (Request == ?SSH_FXP_RENAME orelse
95+
+ Request == ?SSH_FXP_SYMLINK),
96+
+ (PLen > MaxPath orelse PLen2 > MaxPath) ->
97+
+ ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_NO_SUCH_PATH,
98+
+ "No such path"),
99+
+ State;
100+
handle_op(?SSH_FXP_INIT, Version, B, State) when is_binary(B) ->
101+
XF = State#state.xf,
102+
Vsn = lists:min([XF#ssh_xfer.vsn, Version]),
103+
diff --git a/lib/ssh/test/ssh_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl
104+
index 9da2e41..01321ed 100644
105+
--- a/lib/ssh/test/ssh_sftpd_SUITE.erl
106+
+++ b/lib/ssh/test/ssh_sftpd_SUITE.erl
107+
@@ -43,6 +43,7 @@
108+
open_file_dir_v6/1,
109+
read_dir/1,
110+
read_file/1,
111+
+ max_path/1,
112+
real_path/1,
113+
relative_path/1,
114+
relpath/1,
115+
@@ -71,6 +72,7 @@
116+
-define(REG_ATTERS, <<0,0,0,0,1>>).
117+
-define(UNIX_EPOCH, 62167219200).
118+
-define(MAX_HANDLES, 10).
119+
+-define(MAX_PATH, 200).
120+
-define(is_set(F, Bits), ((F) band (Bits)) == (F)).
121+
122+
%%--------------------------------------------------------------------
123+
@@ -84,6 +86,7 @@ all() ->
124+
[open_close_file,
125+
open_close_dir,
126+
read_file,
127+
+ max_path,
128+
read_dir,
129+
write_file,
130+
rename_file,
131+
@@ -177,7 +180,9 @@ init_per_testcase(TestCase, Config) ->
132+
{sftpd_vsn, 6}])],
133+
ssh:daemon(0, [{subsystems, SubSystems}|Options]);
134+
_ ->
135+
- SubSystems = [ssh_sftpd:subsystem_spec([{max_handles, ?MAX_HANDLES}])],
136+
+ SubSystems = [ssh_sftpd:subsystem_spec(
137+
+ [{max_handles, ?MAX_HANDLES},
138+
+ {max_path, ?MAX_PATH}])],
139+
ssh:daemon(0, [{subsystems, SubSystems}|Options])
140+
end,
141+
142+
@@ -333,6 +338,23 @@ read_file(Config) when is_list(Config) ->
143+
ct:log("Message: ~s", [Msg]),
144+
ok.
145+
146+
+%%--------------------------------------------------------------------
147+
+max_path(Config) when is_list(Config) ->
148+
+ PrivDir = proplists:get_value(priv_dir, Config),
149+
+ FileName = filename:join(PrivDir, "test.txt"),
150+
+ {Cm, Channel} = proplists:get_value(sftp, Config),
151+
+ %% verify max_path limit
152+
+ LongFileName =
153+
+ filename:join(PrivDir,
154+
+ "t" ++ lists:flatten(lists:duplicate(?MAX_PATH, "e")) ++ "st.txt"),
155+
+ {ok, _} = file:copy(FileName, LongFileName),
156+
+ ReqId1 = req_id(),
157+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId1), ?UINT32(?SSH_FX_NO_SUCH_PATH),
158+
+ _/binary>>, _} =
159+
+ open_file(LongFileName, Cm, Channel, ReqId1,
160+
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
161+
+ ?SSH_FXF_OPEN_EXISTING).
162+
+
163+
read_dir(Config) when is_list(Config) ->
164+
PrivDir = proplists:get_value(priv_dir, Config),
165+
{Cm, Channel} = proplists:get_value(sftp, Config),
166+
@@ -396,35 +418,33 @@ rename_file(Config) when is_list(Config) ->
167+
PrivDir = proplists:get_value(priv_dir, Config),
168+
FileName = filename:join(PrivDir, "test.txt"),
169+
NewFileName = filename:join(PrivDir, "test1.txt"),
170+
- ReqId = 0,
171+
+ LongFileName =
172+
+ filename:join(PrivDir,
173+
+ "t" ++ lists:flatten(lists:duplicate(?MAX_PATH, "e")) ++ "st.txt"),
174+
{Cm, Channel} = proplists:get_value(sftp, Config),
175+
-
176+
- {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
177+
- ?UINT32(?SSH_FX_OK), _/binary>>, _} =
178+
- rename(FileName, NewFileName, Cm, Channel, ReqId, 6, 0),
179+
-
180+
- NewReqId = ReqId + 1,
181+
-
182+
- {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId),
183+
- ?UINT32(?SSH_FX_OK), _/binary>>, _} =
184+
- rename(NewFileName, FileName, Cm, Channel, NewReqId, 6,
185+
- ?SSH_FXP_RENAME_OVERWRITE),
186+
-
187+
- NewReqId1 = NewReqId + 1,
188+
- file:copy(FileName, NewFileName),
189+
-
190+
- %% No overwrite
191+
- {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId1),
192+
- ?UINT32(?SSH_FX_FILE_ALREADY_EXISTS), _/binary>>, _} =
193+
- rename(FileName, NewFileName, Cm, Channel, NewReqId1, 6,
194+
- ?SSH_FXP_RENAME_NATIVE),
195+
-
196+
- NewReqId2 = NewReqId1 + 1,
197+
-
198+
- {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId2),
199+
- ?UINT32(?SSH_FX_OP_UNSUPPORTED), _/binary>>, _} =
200+
- rename(FileName, NewFileName, Cm, Channel, NewReqId2, 6,
201+
- ?SSH_FXP_RENAME_ATOMIC).
202+
+ Version = 6,
203+
+ [begin
204+
+ case Action of
205+
+ {Code, AFile, BFile, Flags} ->
206+
+ ReqId = req_id(),
207+
+ ct:log("ReqId = ~p,~nCode = ~p,~nAFile = ~p,~nBFile = ~p,~nFlags = ~p",
208+
+ [ReqId, Code, AFile, BFile, Flags]),
209+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId), ?UINT32(Code), _/binary>>, _} =
210+
+ rename(AFile, BFile, Cm, Channel, ReqId, Version, Flags);
211+
+ {file_copy, AFile, BFile} ->
212+
+ {ok, _} = file:copy(AFile, BFile)
213+
+ end
214+
+ end ||
215+
+ Action <-
216+
+ [{?SSH_FX_OK, FileName, NewFileName, 0},
217+
+ {?SSH_FX_OK, NewFileName, FileName, ?SSH_FXP_RENAME_OVERWRITE},
218+
+ {file_copy, FileName, NewFileName},
219+
+ %% no overwrite
220+
+ {?SSH_FX_FILE_ALREADY_EXISTS, FileName, NewFileName, ?SSH_FXP_RENAME_NATIVE},
221+
+ {?SSH_FX_OP_UNSUPPORTED, FileName, NewFileName, ?SSH_FXP_RENAME_ATOMIC},
222+
+ %% max_path
223+
+ {?SSH_FX_NO_SUCH_PATH, FileName, LongFileName, 0}]],
224+
+ ok.
225+
226+
%%--------------------------------------------------------------------
227+
mk_rm_dir(Config) when is_list(Config) ->
228+
--
229+
2.43.0
230+

SPECS/erlang/erlang.spec

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Summary: erlang
33
Name: erlang
44
Version: 25.3.2.21
5-
Release: 3%{?dist}
5+
Release: 4%{?dist}
66
License: Apache-2.0
77
Vendor: Microsoft Corporation
88
Distribution: Mariner
@@ -18,6 +18,7 @@ Patch0: CVE-2025-4748.patch
1818
Patch1: CVE-2025-48038.patch
1919
Patch2: CVE-2025-48040.patch
2020
Patch3: CVE-2025-48041.patch
21+
Patch4: CVE-2025-48039.patch
2122

2223
%description
2324
erlang programming language
@@ -51,6 +52,9 @@ make
5152
%{_libdir}/erlang/*
5253

5354
%changelog
55+
* Fri Oct 03 2025 Akhila Guruju <[email protected]> - 25.3.2.21-4
56+
- Patch for CVE-2025-48039
57+
5458
* Sat Sep 13 2025 Azure Linux Security Servicing Account <[email protected]> - 25.3.2.21-3
5559
- Patch for CVE-2025-48041, CVE-2025-48040, CVE-2025-48038
5660

0 commit comments

Comments
 (0)