Add test for mbedtls_net_poll beyond FD_SETSIZE

mbedtls_net_poll() and mbedtls_net_recv_timeout() rely on select(),
which represents sets of file descriptors through the fd_set type.
This type cannot hold file descriptors larger than FD_SETSIZE. Make
sure that these functions identify this failure code.

Without a proper range check of the file descriptor in the
mbedtls_net_xxx function, this test fails when running with UBSan:
```
net_poll beyond FD_SETSIZE ........................................ source/library/net_sockets.c:482:9: runtime error: index 16 out of bounds for type '__fd_mask [16]'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior source/library/net_sockets.c:482:9 in
```
This is a non-regression test for
https://github.com/ARMmbed/mbedtls/issues/4169 .

The implementation of this test is specific to Unix-like platforms.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
This commit is contained in:
Gilles Peskine 2021-02-24 19:41:29 +01:00
parent 1c0e48a2ce
commit 6667a78c9b
2 changed files with 106 additions and 0 deletions

View File

@ -4,3 +4,5 @@ context_init_free:0
Context init-free-init-free
context_init_free:1
net_poll beyond FD_SETSIZE
poll_beyond_fd_setsize:

View File

@ -2,6 +2,50 @@
#include "mbedtls/net_sockets.h"
#if defined(unix) || defined(__unix__) || defined(__unix) || \
defined(__APPLE__) || defined(__QNXNTO__) || \
defined(__HAIKU__) || defined(__midipix__)
#define MBEDTLS_PLATFORM_IS_UNIXLIKE
#endif
#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE)
#include <sys/fcntl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE)
/** Open a file on the given file descriptor.
*
* This is disruptive if there is already something open on that descriptor.
* Caller beware.
*
* \param ctx An initialized, but unopened socket context.
* On success, it refers to the opened file (\p wanted_fd).
* \param wanted_fd The desired file descriptor.
*
* \return \c 0 on succes, a negative error code on error.
*/
static int open_file_on_fd( mbedtls_net_context *ctx, int wanted_fd )
{
int got_fd = open( "/dev/null", O_RDONLY );
TEST_ASSERT( got_fd >= 0 );
if( got_fd != wanted_fd )
{
TEST_ASSERT( dup2( got_fd, wanted_fd ) >= 0 );
TEST_ASSERT( close( got_fd ) >= 0 );
}
ctx->fd = wanted_fd;
return( 0 );
exit:
return( -1 );
}
#endif /* MBEDTLS_PLATFORM_IS_UNIXLIKE */
/* END_HEADER */
/* BEGIN_DEPENDENCIES
@ -26,3 +70,63 @@ void context_init_free( int reinit )
goto exit;
}
/* END_CASE */
/* BEGIN_CASE depends_on:MBEDTLS_PLATFORM_IS_UNIXLIKE */
void poll_beyond_fd_setsize( )
{
/* Test that mbedtls_net_poll does not misbehave when given a file
* descriptor beyond FD_SETSIZE. This code is specific to platforms
* with a Unix-like select() function. */
struct rlimit rlim_nofile;
int restore_rlim_nofile = 0;
int ret;
mbedtls_net_context ctx;
uint8_t buf[1];
mbedtls_net_init( &ctx );
/* On many systems, by default, the maximum permitted file descriptor
* number is less or equal to FD_SETSIZE. If so, raise the limit if
* possible.
*
* If the limit can't be raised, a newly open file descriptor
* won't be higher than FD_SETSIZE, so the test is not necessary and we
* mark it as skipped.
*/
TEST_ASSERT( getrlimit( RLIMIT_NOFILE, &rlim_nofile ) == 0 );
if( rlim_nofile.rlim_cur <= FD_SETSIZE + 1 )
{
rlim_t old_rlim_cur = rlim_nofile.rlim_cur;
rlim_nofile.rlim_cur = FD_SETSIZE + 1;
TEST_ASSUME( setrlimit( RLIMIT_NOFILE, &rlim_nofile ) == 0 );
rlim_nofile.rlim_cur = old_rlim_cur;
restore_rlim_nofile = 1;
}
TEST_ASSERT( open_file_on_fd( &ctx, FD_SETSIZE ) == 0 );
/* In principle, mbedtls_net_poll() with valid arguments should succeed.
* However, we know that on Unix-like platforms (and others), this function
* is implemented on top of select() and fd_set, which do not support
* file descriptors beyond FD_SETSIZE. So we expect to hit this platform
* limitation.
*
* If mbedtls_net_poll() does not proprely check that ctx.fd is in range,
* it may still happen to return the expected failure code, but if this
* is problematic on the particular platform where the code is running,
* a memory sanitizer such as UBSan should catch it.
*/
ret = mbedtls_net_poll( &ctx, MBEDTLS_NET_POLL_READ, 0 );
TEST_EQUAL( ret, MBEDTLS_ERR_NET_POLL_FAILED );
/* mbedtls_net_recv_timeout() uses select() and fd_set in the same way. */
ret = mbedtls_net_recv_timeout( &ctx, buf, sizeof( buf ), 0 );
TEST_EQUAL( ret, MBEDTLS_ERR_NET_POLL_FAILED );
exit:
mbedtls_net_free( &ctx );
if( restore_rlim_nofile )
setrlimit( RLIMIT_NOFILE, &rlim_nofile );
}
/* END_CASE */