summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Zegenhagen <stefan.zegenhagen@arcutronix.com>2013-05-07 16:22:10 +0200
committerFredrik Gustafsson <fredrik@erlang.org>2013-05-08 10:05:07 +0200
commita36834832a7f0afe5f1c82a70a36f40154f80711 (patch)
tree704d057595809434eaa121af73327f3dd556587a
parent8e00f4ce7a49b2fd1da7e481dc0985703e4131a5 (diff)
downloaderlang-a36834832a7f0afe5f1c82a70a36f40154f80711.tar.gz
Limit maximum line length in interactive shells
Dear all, it seems as if I sent around the wrong version of the patch. Please find the correct version attached to this e-mail. Kind regards, > one of our testers found out that he could reliably reboot our > erlang-based device by just sending tons of 'A' characters to an > interactive shell. > > It appears that the I/O server of the interactive shell (in group.erl) > is always reading a full line before doing any end-of-input processing, > so that by sending bytes without a newline in between, it slowly eats up > all available memory. > > The patch attached to this e-mail fixes that by introducing a new > io:setopt() option in group.erl named 'max_length' that is used to check > whether the current line length exceeds this maximum whenever the line > is edited. If an overlong line is detected, max_length bytes are > returned immediately (without the actual stop condition being fulfilled, > though, allowing I/O clients to detect this situation). > > max_length is allowed to be an integer() > 0 or 'unlimited'. The default > is 'unlimited' to have the old behaviour by default. > > > > Kind regards, > > _______________________________________________ > erlang-patches mailing list > erlang-patches@erlang.org > http://erlang.org/mailman/listinfo/erlang-patches -- Dr. Stefan Zegenhagen arcutronix GmbH Garbsener Landstr. 10 30419 Hannover Germany Tel: +49 511 277-2734 Fax: +49 511 277-2709 Email: stefan.zegenhagen@arcutronix.com Web: www.arcutronix.com *Synchronize the Ethernet* General Managers: Dipl. Ing. Juergen Schroeder, Dr. Josef Gfrerer - Legal Form: GmbH, Registered office: Hannover, HRB 202442, Amtsgericht Hannover; Ust-Id: DE257551767. Please consider the environment before printing this message. >From b8b904f4ca1e0b418f8e758c9aa5e43b6469c3b4 Mon Sep 17 00:00:00 2001 From: Stefan Zegenhagen <stefan.zegenhagen@arcutronix.com> Date: Tue, 7 May 2013 14:14:50 +0200 Subject: [PATCH] limit line length in interactive shells Since the I/O server in group.erl was always collecting full lines before doing any further end-of-input processing, it was possible to crash any erlang system by just sending tons of characters without any newline to an interactive shell. Fix that by allowing to specify a maximum input length. Whenever new characters are received from the input device, check whether the new input exceeds the maximum line length and if so, return the data that was read so far (up to the maximum line length characters).
-rw-r--r--lib/kernel/src/group.erl97
-rw-r--r--lib/stdlib/doc/src/io.xml9
-rw-r--r--lib/stdlib/doc/src/io_protocol.xml4
-rw-r--r--lib/stdlib/src/io.erl4
4 files changed, 101 insertions, 13 deletions
diff --git a/lib/kernel/src/group.erl b/lib/kernel/src/group.erl
index ff835e1047..8315744eec 100644
--- a/lib/kernel/src/group.erl
+++ b/lib/kernel/src/group.erl
@@ -39,6 +39,7 @@ server(Drv, Shell, Options) ->
proplists:get_value(expand_fun, Options,
fun(B) -> edlin_expand:expand(B) end)),
put(echo, proplists:get_value(echo, Options, true)),
+ put(max_length, unlimited),
start_shell(Shell),
server_loop(Drv, get(shell), []).
@@ -351,12 +352,17 @@ check_valid_opts([{echo,_}|T]) ->
check_valid_opts(T);
check_valid_opts([{expand_fun,_}|T]) ->
check_valid_opts(T);
+check_valid_opts([{max_length, X} | T]) when is_integer(X) andalso X > 0 ->
+ check_valid_opts(T);
+check_valid_opts([{max_length, unlimited} | T]) ->
+ check_valid_opts(T);
check_valid_opts(_) ->
false.
do_setopts(Opts, Drv, Buf) ->
put(expand_fun, proplists:get_value(expand_fun, Opts, get(expand_fun))),
put(echo, proplists:get_value(echo, Opts, get(echo))),
+ put(max_length, proplists:get_value(max_length, Opts, get(max_length))),
case proplists:get_value(encoding,Opts) of
Valid when Valid =:= unicode; Valid =:= utf8 ->
set_unicode_state(Drv,true);
@@ -398,11 +404,17 @@ getopts(Drv,Buf) ->
_ ->
false
end},
+ Max = {max_length, case get(max_length) of
+ Int when is_integer(Int) ->
+ Int;
+ _ ->
+ unlimited
+ end},
Uni = {encoding, case get_unicode_state(Drv) of
true -> unicode;
_ -> latin1
end},
- {ok,[Exp,Echo,Bin,Uni],Buf}.
+ {ok,[Exp,Echo,Bin,Max,Uni],Buf}.
%% get_chars(Prompt, Module, Function, XtraArgument, Drv, Buffer)
@@ -439,6 +451,8 @@ get_chars_loop(Pbs, M, F, Xa, Drv, Buf0, State, Encoding) ->
case Result of
{done,Line,Buf1} ->
get_chars_apply(Pbs, M, F, Xa, Drv, Buf1, State, Line, Encoding);
+ {overlong, Line, Rest} ->
+ {ok, Line, Rest};
interrupted ->
{error,{error,interrupted},[]};
terminated ->
@@ -471,13 +485,17 @@ err_func(_, F, _) ->
get_line(Chars, Pbs, Drv, Encoding) ->
{more_chars,Cont,Rs} = edlin:start(Pbs),
send_drv_reqs(Drv, Rs),
- get_line1(edlin:edit_line(Chars, Cont), Drv, new_stack(get(line_buffer)),
+ get_line1_limit(edlin:edit_line(Chars, Cont), Drv, new_stack(get(line_buffer)),
Encoding).
get_line1({done,Line,Rest,Rs}, Drv, Ls, _Encoding) ->
send_drv_reqs(Drv, Rs),
save_line_buffer(Line, get_lines(Ls)),
{done,Line,Rest};
+get_line1({overlong, Line, Rest, Rs}, Drv, Ls, _Encoding) ->
+ send_drv_reqs(Drv, Rs),
+ save_line_buffer(Line, get_lines(Ls)),
+ {overlong, Line, Rest};
get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Ls0, Encoding)
when ((Mode =:= none) and (Char =:= $\^P))
or ((Mode =:= meta_left_sq_bracket) and (Char =:= $A)) ->
@@ -485,7 +503,7 @@ get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Ls0, Encoding)
case up_stack(save_line(Ls0, edlin:current_line(Cont))) of
{none,_Ls} ->
send_drv(Drv, beep),
- get_line1(edlin:edit_line(Cs, Cont), Drv, Ls0, Encoding);
+ get_line1_limit(edlin:edit_line(Cs, Cont), Drv, Ls0, Encoding);
{Lcs,Ls} ->
send_drv_reqs(Drv, edlin:erase_line(Cont)),
{more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)),
@@ -502,7 +520,7 @@ get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Ls0, Encoding)
case down_stack(save_line(Ls0, edlin:current_line(Cont))) of
{none,_Ls} ->
send_drv(Drv, beep),
- get_line1(edlin:edit_line(Cs, Cont), Drv, Ls0, Encoding);
+ get_line1_limit(edlin:edit_line(Cs, Cont), Drv, Ls0, Encoding);
{Lcs,Ls} ->
send_drv_reqs(Drv, edlin:erase_line(Cont)),
{more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)),
@@ -548,11 +566,11 @@ get_line1({expand, Before, Cs0, Cont,Rs}, Drv, Ls0, Encoding) ->
send_drv(Drv, {put_chars, unicode, unicode:characters_to_binary(MatchStr,unicode)}),
[$\^L | Cs1]
end,
- get_line1(edlin:edit_line(Cs, Cont), Drv, Ls0, Encoding);
+ get_line1_limit(edlin:edit_line(Cs, Cont), Drv, Ls0, Encoding);
get_line1({undefined,_Char,Cs,Cont,Rs}, Drv, Ls, Encoding) ->
send_drv_reqs(Drv, Rs),
send_drv(Drv, beep),
- get_line1(edlin:edit_line(Cs, Cont), Drv, Ls, Encoding);
+ get_line1_limit(edlin:edit_line(Cs, Cont), Drv, Ls, Encoding);
%% The search item was found and accepted (new line entered on the exact
%% result found)
get_line1({_What,Cont={line,_Prompt,_Chars,search_found},Rs}, Drv, Ls0, Encoding) ->
@@ -608,7 +626,7 @@ get_line1({What,Cont0,Rs}, Drv, Ls, Encoding) ->
more_data(What, Cont0, Drv, Ls, Encoding) ->
receive
{Drv,{data,Cs}} ->
- get_line1(edlin:edit_line(Cs, Cont0), Drv, Ls, Encoding);
+ get_line1_limit(edlin:edit_line(Cs, Cont0), Drv, Ls, Encoding);
{Drv,eof} ->
get_line1(edlin:edit_line(eof, Cont0), Drv, Ls, Encoding);
{io_request,From,ReplyAs,Req} when is_pid(From) ->
@@ -626,14 +644,35 @@ more_data(What, Cont0, Drv, Ls, Encoding) ->
get_line1(edlin:edit_line([], Cont0), Drv, Ls, Encoding)
end.
+get_line1_limit({more_chars, Cont, Rs}, Drv, Ls, Encoding) ->
+ Total = edlin:length_before(Cont) + edlin:length_after(Cont),
+ case over_maxlength(Total) of
+ true ->
+ % we've hit the maximum line length. terminate the current
+ % line by faking EOF
+ send_drv_reqs(Drv, Rs),
+ {done, Line, Cs, Rs0} = edlin:edit_line(eof, Cont),
+ % we need to output a newline here...
+ Rs1 = Rs0 ++ [{put_chars,unicode,"\n"}],
+ % and we better split the line by the maximum amount
+ % of input we are expected to return
+ {Ret, Keep} = lists:split(get(max_length), Line),
+ get_line1({overlong, Ret, Keep ++ Cs, Rs1}, Drv, Ls, Encoding);
+ false ->
+ get_line1({more_chars, Cont, Rs}, Drv, Ls, Encoding)
+ end;
+get_line1_limit(Line, Drv, Ls, Encoding) ->
+ get_line1(Line, Drv, Ls, Encoding).
+
+
get_line_echo_off(Chars, Pbs, Drv) ->
send_drv_reqs(Drv, [{put_chars, unicode,Pbs}]),
- get_line_echo_off1(edit_line(Chars,[]), Drv).
+ get_line_echo_off1_limit(edit_line(Chars,[]), Drv).
get_line_echo_off1({Chars,[]}, Drv) ->
receive
{Drv,{data,Cs}} ->
- get_line_echo_off1(edit_line(Cs, Chars), Drv);
+ get_line_echo_off1_limit(edit_line(Cs, Chars), Drv);
{Drv,eof} ->
get_line_echo_off1(edit_line(eof, Chars), Drv);
{io_request,From,ReplyAs,Req} when is_pid(From) ->
@@ -647,6 +686,19 @@ get_line_echo_off1({Chars,[]}, Drv) ->
get_line_echo_off1({Chars,Rest}, _Drv) ->
{done,lists:reverse(Chars),case Rest of done -> []; _ -> Rest end}.
+get_line_echo_off1_limit({Chars, []}, Drv) ->
+ case over_maxlength(length(Chars)) of
+ true ->
+ % we've hit the maximum line length...
+ {Line, Rest} = lists:split(get(max_length), Chars),
+ {overlong, Line, Rest};
+ false ->
+ get_line_echo_off1({Chars, []}, Drv)
+ end;
+get_line_echo_off1_limit(Line, Drv) ->
+ get_line_echo_off1(Line, Drv).
+
+
%% We support line editing for the ICANON mode except the following
%% line editing characters, which already has another meaning in
%% echo-on mode (See Advanced Programming in the Unix Environment, 2nd ed,
@@ -775,12 +827,12 @@ search_down_stack(Stack, Substr) ->
%% This is get_line without line editing (except for backspace) and
%% without echo.
get_password_line(Chars, Drv) ->
- get_password1(edit_password(Chars,[]),Drv).
+ get_password1_limit(edit_password(Chars,[]),Drv).
get_password1({Chars,[]}, Drv) ->
receive
{Drv,{data,Cs}} ->
- get_password1(edit_password(Cs,Chars),Drv);
+ get_password1_limit(edit_password(Cs,Chars),Drv);
{io_request,From,ReplyAs,Req} when is_pid(From) ->
%send_drv_reqs(Drv, [{delete_chars, -length(Pbs)}]),
io_request(Req, From, ReplyAs, Drv, []), %WRONG!!!
@@ -797,6 +849,20 @@ get_password1({Chars,Rest},Drv) ->
send_drv_reqs(Drv,[{put_chars, unicode, "\n"}]),
{done,lists:reverse(Chars),case Rest of done -> []; _ -> Rest end}.
+
+get_password1_limit({Chars, []}, Drv) ->
+ Line = case over_maxlength(length(Chars)) of
+ true ->
+ % we've hit the maximum line length...
+ lists:split(get(max_length), Chars);
+ false ->
+ {Chars, []}
+ end,
+ get_password1(Line, Drv);
+get_password1_limit(Line, Drv) ->
+ get_password1(Line, Drv).
+
+
edit_password([],Chars) ->
{Chars,[]};
edit_password([$\r],Chars) ->
@@ -832,3 +898,12 @@ append(L1, L2, _) when is_list(L1) ->
L1++L2;
append(_Eof, L, _) ->
L.
+
+
+over_maxlength(LineLength) ->
+ case get(max_length) of
+ Int when is_integer(Int) ->
+ Int < LineLength;
+ _Else ->
+ false
+ end.
diff --git a/lib/stdlib/doc/src/io.xml b/lib/stdlib/doc/src/io.xml
index 90f24c4cbc..a9467557a3 100644
--- a/lib/stdlib/doc/src/io.xml
+++ b/lib/stdlib/doc/src/io.xml
@@ -206,6 +206,7 @@
[{expand_fun,#Fun&lt;group.0.120017273&gt;},
{echo,true},
{binary,false},
+ {max_length,unlimited},
{encoding,unicode}]</pre>
<p>This example is, as can be seen, run in an environment where the terminal supports Unicode input and output.</p>
</desc>
@@ -284,6 +285,14 @@
<p><c>{encoding, utf8}</c> will have the same effect as <c>{encoding, unicode}</c> on files.</p>
<p>The extended encodings are only supported on disk files (opened by the <seealso marker="kernel:file#open/2">file:open/2</seealso> function)</p>
</item>
+ <tag><c>{max_length, 'unlimited' | pos_integer()}</c></tag>
+ <item>
+ <p>Denotes the maximum line length that the I/O server shall handle. If lines
+ longer than this value are entered, input processing stops at max_length characters
+ and the input read up to here is returned. The default value is <c>unlimited</c> which
+ means that no input length restriction is active.</p>
+ <p>This option is supported by the standard shell only (<c>group.erl</c>).</p>
+ </item>
</taglist>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/io_protocol.xml b/lib/stdlib/doc/src/io_protocol.xml
index d36bf2042f..f52d0382f4 100644
--- a/lib/stdlib/doc/src/io_protocol.xml
+++ b/lib/stdlib/doc/src/io_protocol.xml
@@ -334,10 +334,12 @@ understands the following options:</p>
<em>{echo, boolean()}</em><br/>
<em>{expand_fun, fun()}</em><br/>
<em>{encoding, unicode/latin1}</em> (or <em>unicode</em>/<em>latin1</em>)
+<em>{max_length, integer()}</em><br/>
</p>
<p>- of which the <c>binary</c> and <c>encoding</c> options are common for all
-I/O servers in OTP, while <c>echo</c> and <c>expand</c> are valid only for this
+I/O servers in OTP, while <c>echo</c>, <c>expand</c> and <c>max_length</c>
+are valid only for this
I/O server. It is worth noting that the <c>unicode</c> option notifies how
characters are actually put on the physical IO device, i.e. if the
terminal per se is Unicode aware, it does not affect how characters
diff --git a/lib/stdlib/src/io.erl b/lib/stdlib/src/io.erl
index 53728237ca..d0f578b80b 100644
--- a/lib/stdlib/src/io.erl
+++ b/lib/stdlib/src/io.erl
@@ -172,10 +172,12 @@ get_password(Io) ->
-type encoding() :: 'latin1' | 'unicode' | 'utf8' | 'utf16' | 'utf32'
| {'utf16', 'big' | 'little'} | {'utf32','big' | 'little'}.
-type expand_fun() :: fun((term()) -> {'yes'|'no', string(), [string(), ...]}).
+-type max_length() :: 'unlimited' | pos_integer().
-type opt_pair() :: {'binary', boolean()}
| {'echo', boolean()}
| {'expand_fun', expand_fun()}
- | {'encoding', encoding()}.
+ | {'encoding', encoding()}
+ | {'max_length', max_length()}.
-spec getopts() -> [opt_pair()].