Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion include/posix/unistd.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ extern "C" {
#endif

#ifdef CONFIG_POSIX_API
/* File related operations */
/* File related operations. Convention: "sys_name" is a syscall (needs
* prototype in this file for usage). "name" is a normal userspace
* function (implemented as a wrapper for syscall), usable even
* without prototype, per classical C handling. This distinction
* is however implemented on demand, based on the actual usecases seen.
*/
__syscall int sys_close(int file);
__syscall ssize_t sys_write(int file, const void *buffer, size_t count);
__syscall ssize_t sys_read(int file, void *buffer, size_t count);
extern int close(int file);
extern ssize_t write(int file, const void *buffer, size_t count);
extern ssize_t read(int file, void *buffer, size_t count);
Expand Down Expand Up @@ -50,4 +58,8 @@ int usleep(useconds_t useconds);
}
#endif

#ifndef CONFIG_ARCH_POSIX
#include <syscalls/unistd.h>
#endif /* CONFIG_ARCH_POSIX */

#endif /* ZEPHYR_INCLUDE_POSIX_UNISTD_H_ */
61 changes: 58 additions & 3 deletions lib/os/fdtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
#include <kernel.h>
#include <sys/fdtable.h>
#include <sys/speculation.h>
#ifdef CONFIG_POSIX_API
#include <posix/unistd.h>
#endif
#include <syscall_handler.h>

struct fd_entry {
Expand Down Expand Up @@ -167,27 +170,66 @@ int z_alloc_fd(void *obj, const struct fd_op_vtable *vtable)

#ifdef CONFIG_POSIX_API

ssize_t read(int fd, void *buf, size_t sz)
ssize_t z_impl_sys_read(int fd, void *buf, size_t sz)
{
if (_check_fd(fd) < 0) {
return -1;
}

return fdtable[fd].vtable->read(fdtable[fd].obj, buf, sz);
}

#ifdef CONFIG_USERSPACE
ssize_t z_vrfy_sys_read(int fd, void *buf, size_t sz)
{
if (Z_SYSCALL_MEMORY_WRITE(buf, sz)) {
errno = EFAULT;
return -1;
}

return z_impl_sys_read(fd, buf, sz);
}
#include <syscalls/sys_read_mrsh.c>
#endif /* CONFIG_USERSPACE */

/* Normal C function wrapping a corresponding syscall. Required to ensure
* classic C linkage.
*/
ssize_t read(int fd, void *buf, size_t sz)
{
return sys_read(fd, buf, sz);
}
FUNC_ALIAS(read, _read, ssize_t);

ssize_t write(int fd, const void *buf, size_t sz)
ssize_t z_impl_sys_write(int fd, const void *buf, size_t sz)
{
if (_check_fd(fd) < 0) {
return -1;
}

return fdtable[fd].vtable->write(fdtable[fd].obj, buf, sz);
}

#ifdef CONFIG_USERSPACE
ssize_t z_vrfy_sys_write(int fd, const void *buf, size_t sz)
{
Z_OOPS(Z_SYSCALL_MEMORY_READ(buf, sz));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to make a copy of the buf data here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, why?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just looking the existing calls in sockets.c, but indeed we do not copy the data buffer there either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to copy only stuff which may lead to security issues. E.g., if some memory block contains pointers, we need to copy those pointers (not necessarily the entire memory block!), then validate the copies, then let the kernel use copies for dereferencing.

(Yeah, yeah, someone looking for side-channel (e.g., timing) attacks hardening may copy more stuff, e.g. control-dependent data. Good luck with that to whoever will do that.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contents of buf are really application dependent, and generally are not used by the kernel, unless it is a kernel type of socket / block / char device.

Validating the application has access to the memory should be sufficient.


return z_impl_sys_write(fd, buf, sz);
}
#include <syscalls/sys_write_mrsh.c>
#endif /* CONFIG_USERSPACE */

/* Normal C function wrapping a corresponding syscall. Required to ensure
* classic C linkage.
*/
ssize_t write(int fd, const void *buf, size_t sz)
{
return sys_write(fd, buf, sz);
}
FUNC_ALIAS(write, _write, ssize_t);

int close(int fd)
int z_impl_sys_close(int fd)
{
int res;

Expand All @@ -200,6 +242,19 @@ int close(int fd)

return res;
}

#ifdef CONFIG_USERSPACE
ssize_t z_vrfy_sys_close(int fd)
{
return z_impl_sys_close(fd);
}
#include <syscalls/sys_close_mrsh.c>
#endif /* CONFIG_USERSPACE */

int close(int fd)
{
return sys_close(fd);
}
FUNC_ALIAS(close, _close, int);

int fsync(int fd)
Expand Down
5 changes: 5 additions & 0 deletions tests/net/socket/socket_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

#include <ztest_assert.h>

#ifdef CONFIG_POSIX_API
#include <sys/socket.h>
#include <arpa/inet.h>
#else
#include <net/socket.h>
#endif

#define clear_buf(buf) memset(buf, 0, sizeof(buf))

Expand Down
9 changes: 9 additions & 0 deletions tests/net/socket/tcp_rw/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: Apache-2.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awww... why didn't you use socketpair(2)? ... jk. ;-)


cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(socket_tcp_rw)

target_include_directories(app PRIVATE ${ZEPHYR_BASE}/subsys/net/ip)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
35 changes: 35 additions & 0 deletions tests/net/socket/tcp_rw/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Setup for self-contained net testing without requiring a SLIP driver
CONFIG_NET_TEST=y

# General config
CONFIG_NEWLIB_LIBC=y
CONFIG_POSIX_API=y

# Networking config
CONFIG_NETWORKING=y
CONFIG_NET_IPV4=y
CONFIG_NET_IPV6=y
CONFIG_NET_TCP=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_SOCKETS_POSIX_NAMES=y
CONFIG_POSIX_MAX_FDS=20

# Network driver config
CONFIG_NET_LOOPBACK=y
CONFIG_TEST_RANDOM_GENERATOR=y

# Network address config
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_NET_CONFIG_NEED_IPV4=y
CONFIG_NET_CONFIG_NEED_IPV6=y
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"

CONFIG_MAIN_STACK_SIZE=2048
CONFIG_TEST_USERSPACE=y

# The test requires lot of bufs
CONFIG_NET_PKT_TX_COUNT=24

CONFIG_ZTEST=y
CONFIG_ZTEST_STACKSIZE=2048
208 changes: 208 additions & 0 deletions tests/net/socket/tcp_rw/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
* Copyright (c) 2020 Linaro Limited
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <logging/log.h>
LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL);

#include <ztest_assert.h>
#include <fcntl.h>
#include <net/socket.h>

#ifdef CONFIG_POSIX_API
#include <unistd.h>
#endif

#include "../../socket_helpers.h"

#define TEST_STR_SMALL "test"

#define ANY_PORT 0
#define SERVER_PORT 4242

#define MAX_CONNS 5

#define TCP_TEARDOWN_TIMEOUT K_SECONDS(1)

static void test_bind(int sock, struct sockaddr *addr, socklen_t addrlen)
{
zassert_equal(bind(sock, addr, addrlen),
0,
"bind failed");
}

static void test_listen(int sock)
{
zassert_equal(listen(sock, MAX_CONNS),
0,
"listen failed");
}

static void test_connect(int sock, struct sockaddr *addr, socklen_t addrlen)
{
zassert_equal(connect(sock, addr, addrlen),
0,
"connect failed");
}


static void test_accept(int sock, int *new_sock, struct sockaddr *addr,
socklen_t *addrlen)
{
zassert_not_null(new_sock, "null newsock");

*new_sock = accept(sock, addr, addrlen);
zassert_true(*new_sock >= 0, "accept failed");
}

#ifdef CONFIG_POSIX_API
static void test_write(int sock, const void *buf, size_t len)
{
zassert_equal(write(sock, buf, len),
len,
"write failed");
}

static void test_read(int sock)
{
ssize_t recved = 0;
char rx_buf[30] = {0};

recved = read(sock, rx_buf, sizeof(rx_buf));
zassert_equal(recved,
strlen(TEST_STR_SMALL),
"unexpected received bytes");
zassert_equal(strncmp(rx_buf, TEST_STR_SMALL, strlen(TEST_STR_SMALL)),
0,
"unexpected data");
}
#endif

static void test_close(int sock)
{
zassert_equal(close(sock),
0,
"close failed");
}

/* Test that EOF handling works correctly. Should be called with socket
* whose peer socket was closed.
*/
static void test_eof(int sock)
{
char rx_buf[1];
ssize_t recved;

/* Test that EOF properly detected. */
recved = recv(sock, rx_buf, sizeof(rx_buf), 0);
zassert_equal(recved, 0, "");

/* Calling again should be OK. */
recved = recv(sock, rx_buf, sizeof(rx_buf), 0);
zassert_equal(recved, 0, "");

/* Calling when TCP connection is fully torn down should be still OK. */
k_sleep(TCP_TEARDOWN_TIMEOUT);
recved = recv(sock, rx_buf, sizeof(rx_buf), 0);
zassert_equal(recved, 0, "");
}

/* These tests require POSIX API. */
#ifdef CONFIG_POSIX_API

void test_v4_write_read(void)
{
int c_sock;
int s_sock;
int new_sock;
struct sockaddr_in c_saddr;
struct sockaddr_in s_saddr;
struct sockaddr addr;
socklen_t addrlen = sizeof(addr);

prepare_sock_tcp_v4(CONFIG_NET_CONFIG_MY_IPV4_ADDR, ANY_PORT,
&c_sock, &c_saddr);
prepare_sock_tcp_v4(CONFIG_NET_CONFIG_MY_IPV4_ADDR, SERVER_PORT,
&s_sock, &s_saddr);

test_bind(s_sock, (struct sockaddr *)&s_saddr, sizeof(s_saddr));
test_listen(s_sock);

test_connect(c_sock, (struct sockaddr *)&s_saddr, sizeof(s_saddr));
test_write(c_sock, TEST_STR_SMALL, strlen(TEST_STR_SMALL));

test_accept(s_sock, &new_sock, &addr, &addrlen);
zassert_equal(addrlen, sizeof(struct sockaddr_in), "wrong addrlen");

test_read(new_sock);

test_close(c_sock);
test_eof(new_sock);

test_close(new_sock);
test_close(s_sock);

k_sleep(TCP_TEARDOWN_TIMEOUT);
}

void test_v6_write_read(void)
{
int c_sock;
int s_sock;
int new_sock;
struct sockaddr_in6 c_saddr;
struct sockaddr_in6 s_saddr;
struct sockaddr addr;
socklen_t addrlen = sizeof(addr);

prepare_sock_tcp_v6(CONFIG_NET_CONFIG_MY_IPV6_ADDR, ANY_PORT,
&c_sock, &c_saddr);
prepare_sock_tcp_v6(CONFIG_NET_CONFIG_MY_IPV6_ADDR, SERVER_PORT,
&s_sock, &s_saddr);

test_bind(s_sock, (struct sockaddr *)&s_saddr, sizeof(s_saddr));
test_listen(s_sock);

test_connect(c_sock, (struct sockaddr *)&s_saddr, sizeof(s_saddr));
test_write(c_sock, TEST_STR_SMALL, strlen(TEST_STR_SMALL));

test_accept(s_sock, &new_sock, &addr, &addrlen);
zassert_equal(addrlen, sizeof(struct sockaddr_in6), "wrong addrlen");

test_read(new_sock);

test_close(c_sock);
test_eof(new_sock);

test_close(new_sock);
test_close(s_sock);

k_sleep(TCP_TEARDOWN_TIMEOUT);
}

#else /* CONFIG_POSIX_API */

void test_v4_write_read(void)
{
ztest_test_skip();
}

void test_v6_write_read(void)
{
ztest_test_skip();
}

#endif /* CONFIG_POSIX_API */

void test_main(void)
{
ztest_test_suite(
socket_tcp_read_write,
ztest_user_unit_test(test_v4_write_read),
ztest_user_unit_test(test_v6_write_read)
);

ztest_run_test_suite(socket_tcp_read_write);
}
Loading