Browse Source

Merge pull request #218 from seriyps/integer-overflow-guard

Add guards to not silently truncate integer when overflowed. Fixes #202
Sergey Prokhorov 5 years ago
parent
commit
03c88ef368

+ 23 - 1
src/datatypes/epgsql_codec_integer.erl

@@ -13,16 +13,35 @@
 -behaviour(epgsql_codec).
 
 -export([init/2, names/0, encode/3, decode/3, decode_text/3]).
+-export([check_overflow_small/1, check_overflow_int/1, check_overflow_big/1]).
 
 -export_type([data/0]).
 
 %% See table 8.2
 %% https://www.postgresql.org/docs/current/static/datatype-numeric.html
+-define(SMALLINT_MAX, 16#7fff).  % 32767, (2^15 - 1)
+-define(SMALLINT_MIN, -16#8000). % -32768
+-define(INT_MAX, 16#7fffffff).  % 2147483647, (2^31 - 1)
+-define(INT_MIN, -16#80000000). % -2147483648
 -define(BIGINT_MAX, 16#7fffffffffffffff).  % 9223372036854775807, (2^63 - 1)
--define(BIGINT_MIN, -16#7fffffffffffffff). % -9223372036854775807
+-define(BIGINT_MIN, -16#8000000000000000). % -9223372036854775808
 
 -type data() :: ?BIGINT_MIN..?BIGINT_MAX.
 
+check_overflow_small(N) when N >= ?SMALLINT_MIN, N =< ?SMALLINT_MAX -> ok;
+check_overflow_small(N) ->
+    overflow(N, int2).
+
+check_overflow_int(N) when N >= ?INT_MIN, N =< ?INT_MAX -> ok;
+check_overflow_int(N) ->
+    overflow(N, int4).
+
+check_overflow_big(N) when N >= ?BIGINT_MIN, N =< ?BIGINT_MAX -> ok;
+check_overflow_big(N) ->
+    overflow(N, int8).
+
+overflow(N, Type) ->
+    error({integer_overflow, Type, N}).
 
 init(_, _) -> [].
 
@@ -30,10 +49,13 @@ names() ->
     [int2, int4, int8].
 
 encode(N, int2, _) ->
+    check_overflow_small(N),
     <<N:1/big-signed-unit:16>>;
 encode(N, int4, _) ->
+    check_overflow_int(N),
     <<N:1/big-signed-unit:32>>;
 encode(N, int8, _) ->
+    check_overflow_big(N),
     <<N:1/big-signed-unit:64>>.
 
 decode(<<N:1/big-signed-unit:16>>, int2, _)    -> N;

+ 8 - 0
src/datatypes/epgsql_codec_intrange.erl

@@ -51,26 +51,34 @@ encode_int4range({minus_infinity, plus_infinity}) ->
     <<24:1/big-signed-unit:8>>;
 encode_int4range({From, plus_infinity}) ->
     FromInt = to_int(From),
+    epgsql_codec_integer:check_overflow_int(FromInt),
     <<18:1/big-signed-unit:8, 4:?int32, FromInt:?int32>>;
 encode_int4range({minus_infinity, To}) ->
     ToInt = to_int(To),
+    epgsql_codec_integer:check_overflow_int(ToInt),
     <<8:1/big-signed-unit:8, 4:?int32, ToInt:?int32>>;
 encode_int4range({From, To}) ->
     FromInt = to_int(From),
     ToInt = to_int(To),
+    epgsql_codec_integer:check_overflow_int(FromInt),
+    epgsql_codec_integer:check_overflow_int(ToInt),
     <<2:1/big-signed-unit:8, 4:?int32, FromInt:?int32, 4:?int32, ToInt:?int32>>.
 
 encode_int8range({minus_infinity, plus_infinity}) ->
     <<24:1/big-signed-unit:8>>;
 encode_int8range({From, plus_infinity}) ->
     FromInt = to_int(From),
+    epgsql_codec_integer:check_overflow_big(FromInt),
     <<18:1/big-signed-unit:8, 8:?int32, FromInt:?int64>>;
 encode_int8range({minus_infinity, To}) ->
     ToInt = to_int(To),
+    epgsql_codec_integer:check_overflow_big(ToInt),
     <<8:1/big-signed-unit:8, 8:?int32, ToInt:?int64>>;
 encode_int8range({From, To}) ->
     FromInt = to_int(From),
     ToInt = to_int(To),
+    epgsql_codec_integer:check_overflow_big(FromInt),
+    epgsql_codec_integer:check_overflow_big(ToInt),
     <<2:1/big-signed-unit:8, 8:?int32, FromInt:?int64, 8:?int32, ToInt:?int64>>.
 
 to_int(N) when is_integer(N) -> N;

+ 21 - 3
test/epgsql_SUITE.erl

@@ -1,6 +1,6 @@
 -module(epgsql_SUITE).
 
--include_lib("eunit/include/eunit.hrl").
+-include_lib("stdlib/include/assert.hrl").
 -include_lib("common_test/include/ct.hrl").
 -include_lib("public_key/include/public_key.hrl").
 -include("epgsql_tests.hrl").
@@ -780,7 +780,25 @@ numeric_type(Config) ->
     check_type(Config, float4, "1.0", 1.0, [0.0, 1.23456, -1.23456]),
     check_type(Config, float4, "'-Infinity'", minus_infinity, [minus_infinity, plus_infinity, nan]),
     check_type(Config, float8, "1.0", 1.0, [0.0, 1.23456789012345, -1.23456789012345]),
-    check_type(Config, float8, "'nan'", nan, [minus_infinity, plus_infinity, nan]).
+    check_type(Config, float8, "'nan'", nan, [minus_infinity, plus_infinity, nan]),
+    %% Check overflow protection. Connection just crashes for now instead of silently
+    %% truncating the data. Some cleaner behaviour can be introduced later.
+    epgsql_ct:with_connection(Config, fun(C) ->
+        Module = ?config(module, Config),
+        Trap = process_flag(trap_exit, true),
+        try Module:equery(C, "SELECT $1::int2", [32768]) of
+          {error, closed} ->
+                %% epgsqla/epgsqli
+                ok
+        catch exit:Reason ->
+                %% epgsql
+                ?assertMatch({{{integer_overflow, int2, _}, _}, _}, Reason),
+                receive {'EXIT', C, _} -> ok
+                after 1000 -> error(timeout)
+                end
+        end,
+        process_flag(trap_exit, Trap)
+    end).
 
 character_type(Config) ->
     Alpha = unicode:characters_to_binary([16#03B1]),
@@ -1146,7 +1164,7 @@ connection_closed_by_server(Config) ->
                     {'EXIT', C2, {shutdown, #error{code = <<"57P01">>}}} ->
                         P ! ok;
                     Other ->
-                        ?debugFmt("Unexpected msg: ~p~n", [Other]),
+                        ct:pal("Unexpected msg: ~p~n", [Other]),
                         P ! error
                 end
             end)