Skip to content

Commit d5467a9

Browse files
author
José Valim
committed
Ensure recursive file/path ops raise on paths with null bytes
1 parent fa574e6 commit d5467a9

File tree

4 files changed

+45
-10
lines changed

4 files changed

+45
-10
lines changed

lib/elixir/lib/file.ex

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -670,15 +670,21 @@ defmodule File do
670670
File.cp_r "samples", "tmp"
671671
672672
# Same as before, but asks the user how to proceed in case of conflicts
673-
File.cp_r "samples", "tmp", fn(source, destination) ->
673+
File.cp_r "samples", "tmp", fn source, destination ->
674674
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm. ") == "y\n"
675675
end
676676
677677
"""
678678
@spec cp_r(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: {:ok, [binary]} | {:error, posix, binary}
679-
def cp_r(source, destination, callback \\ fn(_, _) -> true end) when is_function(callback, 2) do
680-
source = IO.chardata_to_string(source)
681-
destination = IO.chardata_to_string(destination)
679+
def cp_r(source, destination, callback \\ fn _, _ -> true end) when is_function(callback, 2) do
680+
source =
681+
source
682+
|> IO.chardata_to_string()
683+
|> assert_no_null_byte!("File.cp_r/3")
684+
destination =
685+
destination
686+
|> IO.chardata_to_string()
687+
|> assert_no_null_byte!("File.cp_r/3")
682688

683689
case do_cp_r(source, destination, callback, []) do
684690
{:error, _, _} = error -> error
@@ -947,7 +953,10 @@ defmodule File do
947953
"""
948954
@spec rm_rf(Path.t) :: {:ok, [binary]} | {:error, posix, binary}
949955
def rm_rf(path) do
950-
do_rm_rf(IO.chardata_to_string(path), {:ok, []})
956+
path
957+
|> IO.chardata_to_string()
958+
|> assert_no_null_byte!("File.cp_r/3")
959+
|> do_rm_rf({:ok, []})
951960
end
952961

953962
defp do_rm_rf(path, {:ok, _} = entry) do
@@ -1454,6 +1463,15 @@ defmodule File do
14541463

14551464
@read_ahead_size 64 * 1024
14561465

1466+
defp assert_no_null_byte!(binary, operation) do
1467+
case :binary.match(binary, "\0") do
1468+
{_, _} ->
1469+
raise ArgumentError, "cannot execute #{operation} for path with null byte, got: #{inspect binary}"
1470+
:nomatch ->
1471+
binary
1472+
end
1473+
end
1474+
14571475
defp normalize_modes([:utf8 | rest], binary?) do
14581476
[encoding: :utf8] ++ normalize_modes(rest, binary?)
14591477
end

lib/elixir/lib/path.ex

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -615,17 +615,19 @@ defmodule Path do
615615
def wildcard(glob, opts \\ []) do
616616
mod = if Keyword.get(opts, :match_dot), do: :file, else: Path.Wildcard
617617
glob
618-
|> chardata_to_list()
618+
|> chardata_to_list!()
619619
|> :filelib.wildcard(mod)
620620
|> Enum.map(&IO.chardata_to_string/1)
621621
end
622622

623-
# expand_dot the given path by expanding "..", "." and "~".
624-
625-
defp chardata_to_list(chardata) do
623+
defp chardata_to_list!(chardata) do
626624
case :unicode.characters_to_list(chardata) do
627625
result when is_list(result) ->
628-
result
626+
if 0 in result do
627+
raise ArgumentError, "cannot execute Path.wildcard/2 for path with null byte, got: #{inspect chardata}"
628+
else
629+
result
630+
end
629631

630632
{:error, encoded, rest} ->
631633
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
@@ -654,6 +656,8 @@ defmodule Path do
654656
end
655657
end
656658

659+
# expand_dot the given path by expanding "..", "." and "~".
660+
657661
defp expand_dot(<<"/", rest::binary>>),
658662
do: "/" <> do_expand_dot(rest)
659663
defp expand_dot(<<letter, ":/", rest::binary>>) when letter in ?a..?z,

lib/elixir/test/elixir/file_test.exs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,11 @@ defmodule FileTest do
460460
end
461461
end
462462

463+
test "cp_r raises on path with null byte" do
464+
assert_raise ArgumentError, ~r/null byte/, fn -> File.cp_r("source", "foo\0bar") end
465+
assert_raise ArgumentError, ~r/null byte/, fn -> File.cp_r("foo\0bar", "dest") end
466+
end
467+
463468
test "cp_r with src file and dest file" do
464469
src = fixture_path("file.txt")
465470
dest = tmp_path("sample.txt")
@@ -1113,6 +1118,10 @@ defmodule FileTest do
11131118
refute File.exists?(fixture)
11141119
end
11151120

1121+
test "rm_rf raises on path with null byte" do
1122+
assert_raise ArgumentError, ~r/null byte/, fn -> File.rm_rf("foo\0bar") end
1123+
end
1124+
11161125
test "rm_rf with symlink" do
11171126
from = tmp_path("tmp/from")
11181127
to = tmp_path("tmp/to")

lib/elixir/test/elixir/path_test.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ defmodule PathTest do
3232
File.rm_rf tmp_path("wildcard")
3333
end
3434

35+
test "wildcard/2 raises on null byte" do
36+
assert_raise ArgumentError, ~r/null byte/, fn -> Path.wildcard("foo\0bar") end
37+
end
38+
3539
if windows?() do
3640
describe "Windows" do
3741
test "relative/1" do

0 commit comments

Comments
 (0)