From 662bf6812ef0a4fa80cf137761f9b1b5a93821c0 Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Mon, 19 Jul 2010 22:46:14 +0000 Subject: remove unguarded atom creation to prevent DOS attacks. closes COUCHDB-829 git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@965667 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_db.hrl | 1 - src/couchdb/couch_doc.erl | 2 +- src/couchdb/couch_httpd.erl | 2 +- src/couchdb/couch_httpd_rewrite.erl | 66 +++++++++++++++--------------- src/couchdb/couch_httpd_stats_handlers.erl | 3 +- src/couchdb/couch_httpd_view.erl | 4 +- src/couchdb/couch_os_process.erl | 4 +- src/couchdb/couch_rep.erl | 2 +- src/couchdb/couch_util.erl | 4 +- 9 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl index 4497dc8f..a35745ef 100644 --- a/src/couchdb/couch_db.hrl +++ b/src/couchdb/couch_db.hrl @@ -20,7 +20,6 @@ -define(JSON_ENCODE(V), couch_util:json_encode(V)). -define(JSON_DECODE(V), couch_util:json_decode(V)). --define(b2a(V), list_to_atom(binary_to_list(V))). -define(b2l(V), binary_to_list(V)). -define(l2b(V), list_to_binary(V)). diff --git a/src/couchdb/couch_doc.erl b/src/couchdb/couch_doc.erl index a6d23a91..d15cd7de 100644 --- a/src/couchdb/couch_doc.erl +++ b/src/couchdb/couch_doc.erl @@ -267,7 +267,7 @@ att_encoding_info(BinProps) -> {identity, DiskLen}; Enc -> EncodedLen = couch_util:get_value(<<"encoded_length">>, BinProps, DiskLen), - {list_to_atom(?b2l(Enc)), EncodedLen} + {list_to_existing_atom(?b2l(Enc)), EncodedLen} end. to_doc_info(FullDocInfo) -> diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index 079b9367..faf14bcc 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -225,7 +225,7 @@ handle_request_int(MochiReq, DefaultFun, true -> ?LOG_INFO("MethodOverride: ~s (real method was ~s)", [MethodOverride, Method1]), case Method1 of - 'POST' -> list_to_atom(MethodOverride); + 'POST' -> couch_util:to_existing_atom(MethodOverride); _ -> % Ignore X-HTTP-Method-Override when the original verb isn't POST. % I'd like to send a 406 error to the client, but that'd require a nasty refactor. diff --git a/src/couchdb/couch_httpd_rewrite.erl b/src/couchdb/couch_httpd_rewrite.erl index 6c909994..ca4ac1f0 100644 --- a/src/couchdb/couch_httpd_rewrite.erl +++ b/src/couchdb/couch_httpd_rewrite.erl @@ -20,7 +20,7 @@ -include("couch_db.hrl"). -define(SEPARATOR, $\/). --define(MATCH_ALL, '*'). +-define(MATCH_ALL, {bind, <<"*">>}). %% doc The http rewrite handler. All rewriting is done from @@ -118,7 +118,7 @@ handle_rewrite_req(#httpd{ DesignId = <<"_design/", DesignName/binary>>, Prefix = <<"/", DbName/binary, "/", DesignId/binary>>, QueryList = couch_httpd:qs(Req), - QueryList1 = [{to_atom(K), V} || {K, V} <- QueryList], + QueryList1 = [{to_binding(K), V} || {K, V} <- QueryList], #doc{body={Props}} = DDoc, @@ -132,12 +132,12 @@ handle_rewrite_req(#httpd{ DispatchList = [make_rule(Rule) || {Rule} <- Rules], %% get raw path by matching url to a rule. - RawPath = case try_bind_path(DispatchList, Method, PathParts, + RawPath = case try_bind_path(DispatchList, couch_util:to_binary(Method), PathParts, QueryList1) of no_dispatch_path -> throw(not_found); {NewPathParts, Bindings} -> - Parts = [mochiweb_util:quote_plus(X) || X <- NewPathParts], + Parts = [quote_plus(X) || X <- NewPathParts], % build new path, reencode query args, eventually convert % them to json @@ -183,7 +183,10 @@ handle_rewrite_req(#httpd{ UrlHandlers, DbUrlHandlers, DesignUrlHandlers) end. - +quote_plus({bind, X}) -> + mochiweb_util:quote_plus(X); +quote_plus(X) -> + mochiweb_util:quote_plus(X). %% @doc Try to find a rule matching current url. If none is found %% 404 error not_found is raised @@ -196,15 +199,13 @@ try_bind_path([Dispatch|Rest], Method, PathParts, QueryList) -> case bind_path(PathParts1, PathParts, []) of {ok, Remaining, Bindings} -> Bindings1 = Bindings ++ QueryList, - % we parse query args from the rule and fill % it eventually with bindings vars QueryArgs1 = make_query_list(QueryArgs, Bindings1, []), - % remove params in QueryLists1 that are already in % QueryArgs1 Bindings2 = lists:foldl(fun({K, V}, Acc) -> - K1 = to_atom(K), + K1 = to_binding(K), KV = case couch_util:get_value(K1, QueryArgs1) of undefined -> [{K1, V}]; _V1 -> [] @@ -230,15 +231,15 @@ make_query_list([], _Bindings, Acc) -> Acc; make_query_list([{Key, {Value}}|Rest], Bindings, Acc) -> Value1 = to_json({Value}), - make_query_list(Rest, Bindings, [{to_atom(Key), Value1}|Acc]); + make_query_list(Rest, Bindings, [{to_binding(Key), Value1}|Acc]); make_query_list([{Key, Value}|Rest], Bindings, Acc) when is_binary(Value) -> Value1 = replace_var(Key, Value, Bindings), - make_query_list(Rest, Bindings, [{to_atom(Key), Value1}|Acc]); + make_query_list(Rest, Bindings, [{to_binding(Key), Value1}|Acc]); make_query_list([{Key, Value}|Rest], Bindings, Acc) when is_list(Value) -> Value1 = replace_var(Key, Value, Bindings), - make_query_list(Rest, Bindings, [{to_atom(Key), Value1}|Acc]); + make_query_list(Rest, Bindings, [{to_binding(Key), Value1}|Acc]); make_query_list([{Key, Value}|Rest], Bindings, Acc) -> - make_query_list(Rest, Bindings, [{to_atom(Key), Value}|Acc]). + make_query_list(Rest, Bindings, [{to_binding(Key), Value}|Acc]). replace_var(Key, Value, Bindings) -> case Value of @@ -274,7 +275,7 @@ replace_var(Key, Value, Bindings) -> get_var(VarName, Props, Default) -> - VarName1 = list_to_atom(binary_to_list(VarName)), + VarName1 = to_binding(VarName), couch_util:get_value(VarName1, Props, Default). %% doc: build new patch from bindings. bindings are query args @@ -288,8 +289,8 @@ make_new_path([?MATCH_ALL], _Bindings, Remaining, Acc) -> make_new_path([?MATCH_ALL|_Rest], _Bindings, Remaining, Acc) -> Acc1 = lists:reverse(Acc) ++ Remaining, Acc1; -make_new_path([P|Rest], Bindings, Remaining, Acc) when is_atom(P) -> - P2 = case couch_util:get_value(P, Bindings) of +make_new_path([{bind, P}|Rest], Bindings, Remaining, Acc) -> + P2 = case couch_util:get_value({bind, P}, Bindings) of undefined -> << "undefined">>; P1 -> P1 end, @@ -304,7 +305,7 @@ make_new_path([P|Rest], Bindings, Remaining, Acc) -> %% depending on HTTP method. bind_method(?MATCH_ALL, _Method) -> true; -bind_method(Method, Method) -> +bind_method({bind, Method}, Method) -> true; bind_method(_, _) -> false. @@ -318,8 +319,8 @@ bind_path([?MATCH_ALL], Rest, Bindings) when is_list(Rest) -> {ok, Rest, Bindings}; bind_path(_, [], _) -> fail; -bind_path([Token|RestToken],[Match|RestMatch],Bindings) when is_atom(Token) -> - bind_path(RestToken, RestMatch, [{Token, Match}|Bindings]); +bind_path([{bind, Token}|RestToken],[Match|RestMatch],Bindings) -> + bind_path(RestToken, RestMatch, [{{bind, Token}, Match}|Bindings]); bind_path([Token|RestToken], [Token|RestMatch], Bindings) -> bind_path(RestToken, RestMatch, Bindings); bind_path(_, _, _) -> @@ -350,15 +351,15 @@ normalize_path1([Path|Rest], Acc) -> %% @doc transform json rule in erlang for pattern matching make_rule(Rule) -> Method = case couch_util:get_value(<<"method">>, Rule) of - undefined -> '*'; - M -> list_to_atom(?b2l(M)) + undefined -> ?MATCH_ALL; + M -> to_binding(M) end, QueryArgs = case couch_util:get_value(<<"query">>, Rule) of undefined -> []; {Args} -> Args end, FromParts = case couch_util:get_value(<<"from">>, Rule) of - undefined -> ['*']; + undefined -> [?MATCH_ALL]; From -> parse_path(From) end, @@ -396,30 +397,29 @@ path_to_list([<<"..">>|R], Acc, DotDotCount) -> path_to_list([P|R], Acc, DotDotCount) -> P1 = case P of <<":", Var/binary>> -> - list_to_atom(binary_to_list(Var)); + to_binding(Var); _ -> P end, path_to_list(R, [P1|Acc], DotDotCount). encode_query(Props) -> - Props1 = lists:foldl(fun ({K, V}, Acc) -> - V1 = case is_list(V) of + Props1 = lists:foldl(fun ({{bind, K}, V}, Acc) -> + V1 = case is_list(V) orelse is_binary(V) of true -> V; - false when is_binary(V) -> - V; false -> - mochiweb_util:quote_plus(V) + % probably it's a number + quote_plus(V) end, [{K, V1} | Acc] end, [], Props), lists:flatten(mochiweb_util:urlencode(Props1)). -to_atom(V) when is_atom(V) -> - V; -to_atom(V) when is_binary(V) -> - to_atom(?b2l(V)); -to_atom(V) -> - list_to_atom(V). +to_binding({bind, V}) -> + {bind, V}; +to_binding(V) when is_list(V) -> + to_binding(?l2b(V)); +to_binding(V) -> + {bind, V}. to_json(V) -> iolist_to_binary(?JSON_ENCODE(V)). diff --git a/src/couchdb/couch_httpd_stats_handlers.erl b/src/couchdb/couch_httpd_stats_handlers.erl index 26c8891d..41aeaed0 100644 --- a/src/couchdb/couch_httpd_stats_handlers.erl +++ b/src/couchdb/couch_httpd_stats_handlers.erl @@ -29,7 +29,8 @@ handle_stats_req(#httpd{method='GET', path_parts=[_, _Mod]}) -> handle_stats_req(#httpd{method='GET', path_parts=[_, Mod, Key]}=Req) -> flush(Req), - Stats = couch_stats_aggregator:get_json({?b2a(Mod), ?b2a(Key)}, range(Req)), + Stats = couch_stats_aggregator:get_json({list_to_atom(binary_to_list(Mod)), + list_to_atom(binary_to_list(Key))}, range(Req)), send_json(Req, {[{Mod, {[{Key, Stats}]}}]}); handle_stats_req(#httpd{method='GET', path_parts=[_, _Mod, _Key | _Extra]}) -> diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl index 704136ca..65aa364a 100644 --- a/src/couchdb/couch_httpd_view.erl +++ b/src/couchdb/couch_httpd_view.erl @@ -196,10 +196,10 @@ reverse_key_default(?MAX_STR) -> ?MIN_STR; reverse_key_default(Key) -> Key. get_stale_type(Req) -> - list_to_atom(couch_httpd:qs_value(Req, "stale", "nil")). + list_to_existing_atom(couch_httpd:qs_value(Req, "stale", "nil")). get_reduce_type(Req) -> - list_to_atom(couch_httpd:qs_value(Req, "reduce", "true")). + list_to_existing_atom(couch_httpd:qs_value(Req, "reduce", "true")). load_view(Req, Db, {ViewDesignId, ViewName}, Keys) -> Stale = get_stale_type(Req), diff --git a/src/couchdb/couch_os_process.erl b/src/couchdb/couch_os_process.erl index 070b86fc..5776776b 100644 --- a/src/couchdb/couch_os_process.erl +++ b/src/couchdb/couch_os_process.erl @@ -93,10 +93,10 @@ readjson(OsProc) when is_record(OsProc, os_proc) -> ?LOG_INFO("OS Process ~p Log :: ~s", [OsProc#os_proc.port, Msg]), readjson(OsProc); [<<"error">>, Id, Reason] -> - throw({list_to_atom(binary_to_list(Id)),Reason}); + throw({couch_util:to_existing_atom(Id),Reason}); [<<"fatal">>, Id, Reason] -> ?LOG_INFO("OS Process ~p Fatal Error :: ~s ~p",[OsProc#os_proc.port, Id, Reason]), - throw({list_to_atom(binary_to_list(Id)),Reason}); + throw({couch_util:to_existing_atom(Id),Reason}); Result -> Result end. diff --git a/src/couchdb/couch_rep.erl b/src/couchdb/couch_rep.erl index 4f6fb673..1e36f58e 100644 --- a/src/couchdb/couch_rep.erl +++ b/src/couchdb/couch_rep.erl @@ -367,7 +367,7 @@ strip_password(Url) -> dbinfo(#http_db{} = Db) -> {DbProps} = couch_rep_httpc:request(Db), - [{list_to_atom(?b2l(K)), V} || {K,V} <- DbProps]; + [{list_to_existing_atom(?b2l(K)), V} || {K,V} <- DbProps]; dbinfo(Db) -> {ok, Info} = couch_db:get_db_info(Db), Info. diff --git a/src/couchdb/couch_util.erl b/src/couchdb/couch_util.erl index 2aa69af8..f2bf6297 100644 --- a/src/couchdb/couch_util.erl +++ b/src/couchdb/couch_util.erl @@ -70,9 +70,9 @@ normparts([Part | RestParts], Acc) -> % works like list_to_existing_atom, except can be list or binary and it % gives you the original value instead of an error if no existing atom. to_existing_atom(V) when is_list(V) -> - try list_to_existing_atom(V) catch _ -> V end; + try list_to_existing_atom(V) catch _:_ -> V end; to_existing_atom(V) when is_binary(V) -> - try list_to_existing_atom(?b2l(V)) catch _ -> V end; + try list_to_existing_atom(?b2l(V)) catch _:_ -> V end; to_existing_atom(V) when is_atom(V) -> V. -- cgit v1.2.3