diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index a1419233b9a0eafef65767d4eadcd3f3be6986a2..f2952812f049f045e332f9b341e1edc8ebe6930c 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -1322,6 +1322,7 @@ iproto_connection_on_input(ev_loop *loop, struct ev_io *watcher,
 			return;
 		}
 		/* Read input. */
+		ibuf_reserve(in, ibuf_unused(in));
 		ssize_t nrd = iostream_read(io, in->wpos, ibuf_unused(in));
 		if (nrd < 0) {                  /* Socket is not ready. */
 			if (nrd == IOSTREAM_ERROR)
@@ -1343,7 +1344,7 @@ iproto_connection_on_input(ev_loop *loop, struct ev_io *watcher,
 			      IPROTO_RECEIVED, nrd);
 
 		/* Update the read position and connection state. */
-		in->wpos += nrd;
+		ibuf_alloc(in, nrd);
 		con->parse_size += nrd;
 		/* Enqueue all requests which are fully read up. */
 		if (iproto_enqueue_batch(con, in) != 0)
diff --git a/src/lua/buffer.lua b/src/lua/buffer.lua
index c5cefe32b60c029bde0bcf606b2565985bff1f89..9fc5ee8fda2a5a09d9bb9dad0b95bcbdbf5e5b9b 100644
--- a/src/lua/buffer.lua
+++ b/src/lua/buffer.lua
@@ -1,6 +1,7 @@
 -- buffer.lua (internal file)
 
 local ffi = require('ffi')
+local utils = require('internal.utils')
 local READAHEAD = 16320
 
 ffi.cdef[[
@@ -120,10 +121,19 @@ local function ibuf_recycle(buf)
     builtin.ibuf_reinit(buf)
 end
 
+local function ibuf_poison_unallocated(buf)
+    utils.poison_memory_region(buf.wpos, buf.epos - buf.wpos);
+end
+
+local function ibuf_unpoison_unallocated(buf)
+    utils.unpoison_memory_region(buf.wpos, buf.epos - buf.wpos);
+end
+
 local function ibuf_reset(buf)
     checkibuf(buf, 'reset')
     buf.rpos = buf.buf
     buf.wpos = buf.buf
+    ibuf_poison_unallocated(buf)
 end
 
 local function ibuf_reserve_slow(buf, size)
@@ -137,6 +147,7 @@ end
 local function ibuf_reserve(buf, size)
     checkibuf(buf, 'reserve')
     if buf.wpos + size <= buf.epos then
+        ibuf_unpoison_unallocated(buf)
         return buf.wpos
     end
     return ibuf_reserve_slow(buf, size)
@@ -146,11 +157,18 @@ local function ibuf_alloc(buf, size)
     checkibuf(buf, 'alloc')
     local wpos
     if buf.wpos + size <= buf.epos then
+        --
+        -- In case of using same buffer we need to unpoison newly
+        -- allocated memory after previous ibuf_alloc or poison after
+        -- newly allocated memory after previous ibuf_reserve.
+        --
+        ibuf_unpoison_unallocated(buf)
         wpos = buf.wpos
     else
         wpos = ibuf_reserve_slow(buf, size)
     end
     buf.wpos = buf.wpos + size
+    ibuf_poison_unallocated(buf)
     return wpos
 end
 
diff --git a/src/lua/init.c b/src/lua/init.c
index 354fa52e0d203f91698ffc72655c6fdf99352b8d..10cbb816e25482284c1c6b7cb5abfaf4ae7ee356 100644
--- a/src/lua/init.c
+++ b/src/lua/init.c
@@ -279,6 +279,7 @@ static const char *lua_modules[] = {
 	/* Make it first to affect load of all other modules */
 	"strict", strict_lua,
 	"compat", compat_lua,
+	"internal.utils", utils_lua,
 	"fun", fun_lua,
 	"debug", debug_lua,
 	"tarantool", init_lua,
@@ -303,7 +304,6 @@ static const char *lua_modules[] = {
 	"tap", tap_lua,
 	"help.en_US", help_en_US_lua,
 	"help", help_lua,
-	"internal.utils", utils_lua,
 	"internal.argparse", argparse_lua,
 	"internal.trigger", trigger_lua,
 	"pwd", pwd_lua,
diff --git a/src/lua/utils.lua b/src/lua/utils.lua
index 53797e2834577e8f157a0ffc6f2ba0f8816fa389..b69d64ef459549247771420007061094e931f7bd 100644
--- a/src/lua/utils.lua
+++ b/src/lua/utils.lua
@@ -1,3 +1,6 @@
+local ffi = require('ffi')
+local tarantool = require('tarantool')
+
 local utils = {}
 
 -- Same as type(), but returns 'number' if 'param' is
@@ -111,4 +114,34 @@ function utils.update_param_table(table, defaults)
     return new_table
 end
 
+ffi.cdef[[
+void
+__asan_poison_memory_region(void const volatile *addr, size_t size);
+void
+__asan_unpoison_memory_region(void const volatile *addr, size_t size);
+int
+__asan_address_is_poisoned(void const volatile *addr);
+]]
+
+if tarantool.build.asan then
+    utils.poison_memory_region = function(start, size)
+        ffi.C.__asan_poison_memory_region(start, size)
+    end
+    utils.unpoison_memory_region = function(start, size)
+        ffi.C.__asan_unpoison_memory_region(start, size)
+    end
+    utils.memory_region_is_poisoned = function(start, size)
+        for i = 0, size - 1 do
+            if ffi.C.__asan_address_is_poisoned(start + i) == 0 then
+                return false
+            end
+        end
+        return true
+    end
+else
+    utils.poison_memory_region = function() end
+    utils.unpoison_memory_region = function() end
+    utils.memory_region_is_poisoned = function() return false end
+end
+
 return utils
diff --git a/test/app-luatest/buffer_test.lua b/test/app-luatest/buffer_test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..46a32e2ff0ffd5b753989c8272f527faeb181ed6
--- /dev/null
+++ b/test/app-luatest/buffer_test.lua
@@ -0,0 +1,81 @@
+local tarantool = require('tarantool')
+local buffer = require('buffer')
+local utils = require('internal.utils')
+local ffi = require('ffi')
+local t = require('luatest')
+
+local g = t.group()
+
+g.test_poison = function()
+    t.skip_if(not tarantool.build.asan, 'only make sense with ASAN')
+
+    local buf = buffer.ibuf()
+    local is_poisoned = function(buf, ptr)
+        t.assert(utils.memory_region_is_poisoned(ptr, buf:unused()))
+    end
+
+    -- Test poison on allocation. --
+    local ptr = buf:alloc(99)
+    t.assert(ptr ~= nil)
+    ffi.fill(ptr, 99)
+    is_poisoned(buf, ptr + 99)
+
+    t.assert(buf:unused() > 133)
+    ptr = buf:alloc(133)
+    t.assert(ptr ~= nil)
+    ffi.fill(ptr, 133)
+    is_poisoned(buf, ptr + 133)
+
+    local size = buf:unused() + 77
+    ptr = buf:alloc(size)
+    t.assert(ptr ~= nil)
+    ffi.fill(ptr, size)
+    t.assert_gt(buf:unused(), 0)
+    is_poisoned(buf, ptr + size)
+
+    -- Test poison after reset. --
+    buf:reset()
+    ptr = buf:alloc(0)
+    is_poisoned(buf, ptr)
+
+    -- Test poison on reserve. --
+    ptr = buf:reserve(777)
+    t.assert(ptr ~= nil)
+    t.assert_ge(buf:unused(), 777)
+    ffi.fill(ptr, buf:unused())
+    ptr = buf:alloc(333)
+    t.assert(ptr ~= nil)
+    ffi.fill(ptr, 333)
+    is_poisoned(buf, ptr + 333)
+
+    t.assert_gt(buf:unused(), 888)
+    ptr = buf:reserve(333)
+    t.assert(ptr ~= nil)
+    t.assert_ge(buf:unused(), 333)
+    ffi.fill(ptr, buf:unused())
+    ptr = buf:alloc(888)
+    t.assert(ptr ~= nil)
+    ffi.fill(ptr, 888)
+    is_poisoned(buf, ptr + 888)
+
+    size = buf:unused() + 133;
+    ptr = buf:reserve(size)
+    t.assert(ptr ~= nil)
+    t.assert_ge(buf:unused(), size)
+    ffi.fill(ptr, buf:unused())
+    t.assert_gt(buf:unused(), 0)
+    ptr = buf:alloc(size)
+    t.assert(ptr ~= nil)
+    t.assert_gt(buf:unused(), 0)
+    is_poisoned(buf, ptr + size)
+
+    size = buf:unused(buf) + 221;
+    buf:read(337)
+    ptr = buf:reserve(size)
+    t.assert(ptr ~= nil)
+    t.assert_ge(buf:unused(), size)
+    ffi.fill(ptr, buf:unused())
+    ptr = buf:alloc(size)
+    t.assert_gt(buf:unused(), 0)
+    is_poisoned(buf, ptr + size)
+end
diff --git a/test/box-tap/merger.test.lua b/test/box-tap/merger.test.lua
index 2d313daa3a51a59a290b173dcbeff638d4eed37e..f22aaa5c5b85ffa8f0216c4620accbc369e2b6ae 100755
--- a/test/box-tap/merger.test.lua
+++ b/test/box-tap/merger.test.lua
@@ -44,10 +44,10 @@ local function truncated_msgpack_buffer(data, trunc)
     local len = data:len()
     local buf = buffer.ibuf()
     -- Ensure we have enough buffer to write len + trunc bytes.
-    buf:reserve(len + trunc)
-    local p = buf:alloc(len)
+    local p = buf:reserve(len + trunc)
     -- Ensure len bytes follows with trunc zero bytes.
     ffi.copy(p, data .. string.rep('\0', trunc), len + trunc)
+    buf:alloc(len)
     return buf
 end