Skip to content

Commit

Permalink
Close Denial of Service issue with TCPConnection.expect (ponylang#3197)
Browse files Browse the repository at this point in the history
Prior to this commit, `expect` could be used to create a denial of
service on a Pony application. If you used expect to handle a framed
protocol, then, you'd by default accept any sized `except` value which
could result in huge amounts of memory being allocated. Memory that
could vastly outstrip the max buffer size set for controlling memory
usage.

After this commit, `expect` is partial and will result in an error if
attempting to set an expect value that is greater than our max read
buffer size.

I intended to commit this prior to another PR, but forgot and thought
that I had. As it is, this PR works in conjunction with ponylang#3185 to prevent
expect from being passed that is greater than the max buffer size
thereby deadlocking the logic post ponylang#3185.
  • Loading branch information
SeanTAllen authored and patches11 committed Jun 29, 2019
1 parent 4fbfc1c commit bd3c489
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to the Pony compiler and standard library will be documented
- Cleanup TCPConnection GC-safety mechanism for writev buffers ([PR #3177](https://github.com/ponylang/ponyc/pull/3177))
- Add SSL tests and fix some SSL related bugs ([PR #3174](https://github.com/ponylang/ponyc/pull/3174))
- Fix lib/llvm to support MacOS ([PR #3181](https://github.com/ponylang/ponyc/pull/3181))
- Close Denial of Service issue with TCPConnection.expect ([PR #3197](https://github.com/ponylang/ponyc/pull/3197))

### Added

Expand All @@ -26,6 +27,7 @@ All notable changes to the Pony compiler and standard library will be documented
- Allow use of runtime TCP connect without ASIO one shot ([PR #3171](https://github.com/ponylang/ponyc/pull/3171))
- Simplify buffering in TCPConnection ([PR #3185](https://github.com/ponylang/ponyc/pull/3185))
- Allow for fine-grained control of yielding CPU in TCPConnection ([PR #3197](https://github.com/ponylang/ponyc/pull/3187))
- Make TCPConnection.expect partial ([PR #3197](https://github.com/ponylang/ponyc/pull/3197))

## [0.28.1] - 2019-06-01

Expand Down
67 changes: 63 additions & 4 deletions packages/net/_test.pony
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ actor Main is TestList
end
test(_TestTCPWritev)
test(_TestTCPExpect)
test(_TestTCPExpectOverBufferSize)
test(_TestTCPMute)
test(_TestTCPUnmute)
ifdef not windows then
Expand Down Expand Up @@ -217,12 +218,29 @@ class iso _TestTCPExpect is UnitTest
fun exclusion_group(): String => "network"

fun ref apply(h: TestHelper) =>
h.expect_action("client connect")
h.expect_action("client receive")
h.expect_action("server receive")
h.expect_action("expect received")

_TestTCP(h)(_TestTCPExpectNotify(h, false), _TestTCPExpectNotify(h, true))

class iso _TestTCPExpectOverBufferSize is UnitTest
"""
Test expecting framed data with TCP.
"""
fun name(): String => "net/TCP.expectoverbuffersize"
fun label(): String => "unreliable-osx"
fun exclusion_group(): String => "network"

fun ref apply(h: TestHelper) =>
h.expect_action("client connect")
h.expect_action("connected")
h.expect_action("accepted")

_TestTCP(h)(_TestTCPExpectOverBufferSizeNotify(h),
_TestTCPExpectOverBufferSizeNotify(h))

primitive _SSLContext
fun val apply(h: TestHelper): (SSL iso^, SSL iso^) ? =>
let sslctx =
Expand Down Expand Up @@ -282,16 +300,24 @@ class _TestTCPExpectNotify is TCPConnectionNotify

fun ref accepted(conn: TCPConnection ref) =>
conn.set_nodelay(true)
conn.expect(_expect)
_send(conn, "hi there")
try
conn.expect(_expect)?
_send(conn, "hi there")
else
_h.fail("expect threw an error")
end

fun ref connect_failed(conn: TCPConnection ref) =>
_h.fail_action("client connect failed")

fun ref connected(conn: TCPConnection ref) =>
_h.complete_action("client connect")
conn.set_nodelay(true)
conn.expect(_expect)
try
conn.expect(_expect)?
else
_h.fail("expect threw an error")
end

fun ref expect(conn: TCPConnection ref, qty: USize): USize =>
_h.complete_action("expect received")
Expand Down Expand Up @@ -326,7 +352,11 @@ class _TestTCPExpectNotify is TCPConnectionNotify
_expect = 4
end

conn.expect(_expect)
try
conn.expect(_expect)?
else
_h.fail("expect threw an error")
end
true

fun ref _send(conn: TCPConnection ref, data: String) =>
Expand All @@ -343,6 +373,35 @@ class _TestTCPExpectNotify is TCPConnectionNotify
buf.append(data)
conn.write(consume buf)

class _TestTCPExpectOverBufferSizeNotify is TCPConnectionNotify
let _h: TestHelper
let _expect: USize = 6_000_000_000

new iso create(h: TestHelper) =>
_h = h

fun ref connect_failed(conn: TCPConnection ref) =>
_h.fail_action("client connect failed")

fun ref accepted(conn: TCPConnection ref) =>
conn.set_nodelay(true)
try
conn.expect(_expect)?
_h.fail("expect didn't error out")
else
_h.complete_action("accepted")
end

fun ref connected(conn: TCPConnection ref) =>
_h.complete_action("client connect")
conn.set_nodelay(true)
try
conn.expect(_expect)?
_h.fail("expect didn't error out")
else
_h.complete_action("connected")
end

class iso _TestTCPWritev is UnitTest
"""
Test writev (and sent/sentv notification).
Expand Down
15 changes: 12 additions & 3 deletions packages/net/tcp_connection.pony
Original file line number Diff line number Diff line change
Expand Up @@ -463,14 +463,23 @@ actor TCPConnection
@pony_os_peername[Bool](_fd, ip)
ip

fun ref expect(qty: USize = 0) =>
fun ref expect(qty: USize = 0) ? =>
"""
A `received` call on the notifier must contain exactly `qty` bytes. If
`qty` is zero, the call can contain any amount of data. This has no effect
if called in the `sent` notifier callback.
Errors if `qty` exceeds the max buffer size as indicated by the
`read_buffer_size` supplied when the connection was created.
"""
if not _in_sent then
_expect = _notify.expect(this, qty)

if qty <= _read_buffer_size then
if not _in_sent then
_expect = _notify.expect(this, qty)
_read_buf_size()
end
else
error
end

fun ref set_nodelay(state: Bool) =>
Expand Down

0 comments on commit bd3c489

Please sign in to comment.