Skip to content

Commit 736406a

Browse files
author
José Valim
committed
Do not allow invalid quoted expression to leak
Closes #2499
1 parent 8869177 commit 736406a

File tree

4 files changed

+83
-32
lines changed

4 files changed

+83
-32
lines changed

lib/elixir/lib/kernel.ex

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2013,7 +2013,7 @@ defmodule Kernel do
20132013
defp do_at([arg], name, function?, env) do
20142014
case function? do
20152015
true ->
2016-
raise ArgumentError, "cannot dynamically set attribute @#{name} inside function"
2016+
raise ArgumentError, "cannot set attribute @#{name} inside function/macro"
20172017
false ->
20182018
case name do
20192019
:behavior ->
@@ -2034,7 +2034,14 @@ defmodule Kernel do
20342034
case function? do
20352035
true ->
20362036
attr = Module.get_attribute(env.module, name, stack)
2037-
:erlang.element(1, :elixir_quote.escape(attr, false))
2037+
try do
2038+
:elixir_quote.escape(attr, false)
2039+
rescue
2040+
e in [ArgumentError] ->
2041+
raise ArgumentError, "cannot inject attribute @#{name} into function/macro because " <> Exception.message(e)
2042+
else
2043+
{val, _} -> val
2044+
end
20382045
false ->
20392046
escaped = case stack do
20402047
[] -> []
@@ -2901,31 +2908,35 @@ defmodule Kernel do
29012908
structure is public should use `@type`.
29022909
"""
29032910
defmacro defstruct(fields) do
2904-
fields =
2905-
quote bind_quoted: [fields: fields] do
2906-
fields = :lists.map(fn
2907-
{key, _} = pair when is_atom(key) -> pair
2908-
key when is_atom(key) -> {key, nil}
2909-
other -> raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
2910-
end, fields)
2911-
2912-
@struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields))
2913-
2914-
case Module.get_attribute(__MODULE__, :derive) do
2915-
[] ->
2916-
:ok
2917-
derive ->
2918-
Protocol.__derive__(derive, __MODULE__, __ENV__)
2919-
end
2911+
quote bind_quoted: [fields: fields] do
2912+
fields = :lists.map(fn
2913+
{key, val} when is_atom(key) ->
2914+
try do
2915+
Macro.escape(val)
2916+
rescue
2917+
e in [ArgumentError] ->
2918+
raise ArgumentError, "invalid value for struct field #{key}, " <> Exception.message(e)
2919+
else
2920+
_ -> {key, val}
2921+
end
2922+
key when is_atom(key) ->
2923+
{key, nil}
2924+
other ->
2925+
raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
2926+
end, fields)
29202927

2921-
@spec __struct__() :: %__MODULE__{}
2922-
def __struct__() do
2923-
@struct
2924-
end
2928+
@struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields))
2929+
2930+
case Module.get_attribute(__MODULE__, :derive) do
2931+
[] -> :ok
2932+
derive -> Protocol.__derive__(derive, __MODULE__, __ENV__)
2933+
end
2934+
2935+
@spec __struct__() :: %__MODULE__{}
2936+
def __struct__() do
2937+
@struct
29252938
end
29262939

2927-
quote do
2928-
unquote(fields)
29292940
fields
29302941
end
29312942
end

lib/elixir/lib/record.ex

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ defmodule Record do
160160
defmacro defrecord(name, tag \\ nil, kv) do
161161
quote bind_quoted: [name: name, tag: tag, kv: kv] do
162162
tag = tag || name
163-
fields = Macro.escape Record.__fields__(:defrecord, kv)
163+
fields = Record.__fields__(:defrecord, kv)
164164

165165
defmacro(unquote(name)(args \\ [])) do
166166
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
@@ -178,7 +178,7 @@ defmodule Record do
178178
defmacro defrecordp(name, tag \\ nil, kv) do
179179
quote bind_quoted: [name: name, tag: tag, kv: kv] do
180180
tag = tag || name
181-
fields = Macro.escape Record.__fields__(:defrecordp, kv)
181+
fields = Record.__fields__(:defrecordp, kv)
182182

183183
defmacrop(unquote(name)(args \\ [])) do
184184
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
@@ -194,9 +194,19 @@ defmodule Record do
194194
@doc false
195195
def __fields__(type, fields) do
196196
:lists.map(fn
197-
{ key, _ } = pair when is_atom(key) -> pair
198-
key when is_atom(key) -> { key, nil }
199-
other -> raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
197+
{key, val} when is_atom(key) ->
198+
try do
199+
Macro.escape(val)
200+
rescue
201+
e in [ArgumentError] ->
202+
raise ArgumentError, "invalid value for record field #{key}, " <> Exception.message(e)
203+
else
204+
val -> {key, val}
205+
end
206+
key when is_atom(key) ->
207+
{key, nil}
208+
other ->
209+
raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
200210
end, fields)
201211
end
202212

lib/elixir/src/elixir_quote.erl

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ do_quote({Left, Right}, Q, E) ->
242242
{TRight, RQ} = do_quote(Right, LQ, E),
243243
{{TLeft, TRight}, RQ};
244244

245-
do_quote(BitString, #elixir_quote{escape=true} = Q, E) when is_bitstring(BitString) ->
245+
do_quote(BitString, #elixir_quote{escape=true} = Q, _) when is_bitstring(BitString) ->
246246
case bit_size(BitString) rem 8 of
247247
0 ->
248248
{BitString, Q};
@@ -260,7 +260,7 @@ do_quote(Tuple, #elixir_quote{escape=true} = Q, E) when is_tuple(Tuple) ->
260260
{{'{}', [], TT}, TQ};
261261

262262
do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
263-
% The improper case is pretty inefficient, but improper lists are are.
263+
%% The improper case is a bit inefficient, but improper lists are rare.
264264
case reverse_improper(List) of
265265
{L} -> do_splice(L, Q, E);
266266
{L, R} ->
@@ -269,14 +269,34 @@ do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
269269
{update_last(TL, fun(X) -> {'|', [], [X, TR]} end), QR}
270270
end;
271271

272+
do_quote(Other, #elixir_quote{escape=true} = Q, _)
273+
when is_number(Other); is_pid(Other); is_atom(Other) ->
274+
{Other, Q};
275+
276+
do_quote(Fun, #elixir_quote{escape=true} = Q, _) when is_function(Fun) ->
277+
case (erlang:fun_info(Fun, env) == {env, []}) andalso
278+
(erlang:fun_info(Fun, type) == {type, external}) of
279+
true -> {Fun, Q};
280+
false -> bad_escape(Fun)
281+
end;
282+
283+
do_quote(Other, #elixir_quote{escape=true}, _) ->
284+
bad_escape(Other);
285+
272286
do_quote(List, Q, E) when is_list(List) ->
273-
do_splice(lists:reverse(List), Q, E);
287+
do_splice(lists:reverse(List), Q, E);
274288

275289
do_quote(Other, Q, _) ->
276290
{Other, Q}.
277291

278292
%% Quote helpers
279293

294+
bad_escape(Arg) ->
295+
Msg = <<"cannot escape ", ('Elixir.Kernel':inspect(Arg, []))/binary, ". ",
296+
"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
297+
"pids and remote functions in the format &Mod.fun/arity">>,
298+
error('Elixir.ArgumentError':exception([{message,Msg}])).
299+
280300
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
281301
All = [meta(Meta, Q), Left, {unquote, Meta, [Expr]}, Args,
282302
Q#elixir_quote.context],

lib/elixir/test/elixir/kernel/errors_test.exs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,16 @@ defmodule Kernel.ErrorsTest do
324324
'casea foo, do: @hello :world'
325325
end
326326

327+
test :invalid_attribute do
328+
msg = ~r"cannot inject attribute @foo into function/macro because cannot escape "
329+
assert_raise ArgumentError, msg, fn ->
330+
defmodule Foo do
331+
@foo fn -> end
332+
def bar, do: @foo
333+
end
334+
end
335+
end
336+
327337
test :invalid_fn_args do
328338
assert_compile_fail TokenMissingError,
329339
"nofile:1: missing terminator: end (for \"fn\" starting at line 1)",

0 commit comments

Comments
 (0)