File 1081-Validate-initial-options.patch of Package erlang

From 5b15656942ac27b1d706e3297f946e34810ba7e8 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen <raimo@erlang.org>
Date: Tue, 10 Feb 2026 18:13:21 +0100
Subject: [PATCH 1/5] Validate initial options

Ensure that relative path components does not allow
a requested file name to go outside the configured root_dir.

root_dir should be checked to be a directory and absolute.

If root_dir is used, Filename should be checked to be
relative under root_dir.
---
 lib/tftp/src/tftp_file.erl | 87 ++++++++++++++++++++++----------------
 1 file changed, 50 insertions(+), 37 deletions(-)

diff --git a/lib/tftp/src/tftp_file.erl b/lib/tftp/src/tftp_file.erl
index b6fb97bfb5..3c6883d69a 100644
--- a/lib/tftp/src/tftp_file.erl
+++ b/lib/tftp/src/tftp_file.erl
@@ -1,7 +1,7 @@
 %%
 %% %CopyrightBegin%
 %% 
-%% Copyright Ericsson AB 2005-2023. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2025. All Rights Reserved.
 %% 
 %% Licensed under the Apache License, Version 2.0 (the "License");
 %% you may not use this file except in compliance with the License.
@@ -43,10 +43,6 @@
 
 -include_lib("kernel/include/file.hrl").
 
--record(initial,
-	{filename,
-	 is_native_ascii}).
-
 -record(state,
 	{access,
 	 filename,
@@ -294,45 +290,62 @@ abort(_Code, _Text, #state{fd = Fd, access = Access} = State) ->
 %%-------------------------------------------------------------------
 
 handle_options(Access, Filename, Mode, Options, Initial) ->
-    I = #initial{filename = Filename, is_native_ascii = is_native_ascii()},
-    {Filename2, IsNativeAscii} = handle_initial(Initial, I),
-    IsNetworkAscii = handle_mode(Mode, IsNativeAscii),
+    {Filename2, IsNativeAscii} = handle_initial(Initial, Filename),
+    IsNetworkAscii =
+        case Mode of
+            "netascii" when IsNativeAscii =:= true ->
+                true;
+            "octet" ->
+                false;
+            _ ->
+                throw({error, {badop, "Illegal mode " ++ Mode}})
+        end,
     Options2 = do_handle_options(Access, Filename2, Options),
     {ok, Filename2, IsNativeAscii, IsNetworkAscii, Options2}.
 
-handle_mode(Mode, IsNativeAscii) ->
-    case Mode of
-	"netascii" when IsNativeAscii =:= true -> true;
-	"octet" -> false;
-	_ -> throw({error, {badop, "Illegal mode " ++ Mode}})
+handle_initial(
+  #state{filename = Filename, is_native_ascii = IsNativeAscii}, _FName) ->
+    {Filename, IsNativeAscii};
+handle_initial(Initial, Filename) when is_list(Initial) ->
+    Opts = get_initial_opts(Initial, #{}),
+    {case Opts of
+         #{ root_dir := RootDir } ->
+             safe_filename(Filename, RootDir);
+         #{} ->
+             Filename
+     end,
+     maps:get(is_native_ascii, Opts, is_native_ascii())}.
+
+get_initial_opts([], Opts) -> Opts;
+get_initial_opts([Opt | Initial], Opts) ->
+    case Opt of
+        {root_dir, RootDir} ->
+            is_map_key(root_dir, Opts) andalso
+                throw({error, {badop, "Internal error. root_dir already set"}}),
+            get_initial_opts(Initial, Opts#{ root_dir => RootDir });
+        {native_ascii, Bool} when is_boolean(Bool) ->
+            get_initial_opts(Initial, Opts#{ is_native_ascii => Bool })
     end.
 
-handle_initial([{root_dir, Dir} | Initial], I) ->
-    case catch filename_join(Dir, I#initial.filename) of
-	{'EXIT', _} ->
-	    throw({error, {badop, "Internal error. root_dir is not a string"}});
-	Filename2 ->
-	    handle_initial(Initial, I#initial{filename = Filename2})
-    end;
-handle_initial([{native_ascii, Bool} | Initial], I) ->
-    case Bool of
-	true  -> handle_initial(Initial, I#initial{is_native_ascii = true});
-	false -> handle_initial(Initial, I#initial{is_native_ascii = false})
-    end;
-handle_initial([], I) when is_record(I, initial) ->
-    {I#initial.filename, I#initial.is_native_ascii};
-handle_initial(State, _) when is_record(State, state) ->
-    {State#state.filename, State#state.is_native_ascii}.
-
-filename_join(Dir, Filename) ->
-    case filename:pathtype(Filename) of
-	absolute ->
-	    [_ | RelFilename] = filename:split(Filename),
-	    filename:join([Dir, RelFilename]);
-	_ ->
-	    filename:join([Dir, Filename])
+safe_filename(Filename, RootDir) ->
+    absolute =:= filename:pathtype(RootDir) orelse
+        throw({error, {badop, "Internal error. root_dir is not absolute"}}),
+    filelib:is_dir(RootDir) orelse
+        throw({error, {badop, "Internal error. root_dir not a directory"}}),
+    RelFilename =
+        case filename:pathtype(Filename) of
+            absolute ->
+                filename:join(tl(filename:split(Filename)));
+            _ -> Filename
+        end,
+    case filelib:safe_relative_path(RelFilename, RootDir) of
+        unsafe ->
+	    throw({error, {badop, "Internal error. Filename out of bounds"}});
+        SafeFilename ->
+            filename:join(RootDir, SafeFilename)
     end.
 
+
 do_handle_options(Access, Filename, [{Key, Val} | T]) ->
     case Key of
 	"tsize" ->
-- 
2.51.0

openSUSE Build Service is sponsored by