From 497246e602c5347cf231f6c0cc48578e340ff8cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adil=20Alperen=20=C3=87iftci?= Date: Wed, 28 Oct 2026 11:36:40 +0300 Subject: [PATCH] [smart] lwp: enforce user buffer validation and copyin/copyout in terminal ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the RT-Smart terminal subsystem, userspace ioctl commands (such as TIOCGWINSZ, TIOCSWINSZ, TIOCGPGRP, TIOCGSID, FIONREAD, and TIOCGPTN) were previously passed directly to underlying FreeBSD ioctl handlers as raw user pointers without kernel staging or copy validation. This patch addresses the user pointer dereference sinks: 1. In lwp_tty_ioctl_adapter(), stage arguments in kernel stack buffers and use _copy_from_user() / _copy_to_user() (lwp_get_from_user / lwp_put_to_user) for TIOCGWINSZ, TIOCSWINSZ, TIOCGPGRP, TIOCSPGRP, TIOCGSID, TIOCSTI, TIOCOUTQ, FIONREAD, and modem commands. 2. In ptm_fops_ioctl(), safely buffer arguments for TIOCGPTN, TIOCPKT, TIOCSIG, TIOCGPGRP, TIOCGSID, FIONREAD, TIOCOUTQ, TIOCGWINSZ, TIOCSWINSZ, and termios settings, returning -ENOTTY on unrecognized commands. 3. In tty_generic_ioctl(), add defense-in-depth boundary checks using lwp_in_user_space() to safeguard kernel memory from arbitrary pointer dereferences. Signed-off-by: Adil Alperen Çiftci --- components/lwp/terminal/freebsd/tty.c | 93 ++++++- components/lwp/terminal/freebsd/tty_compat.c | 115 +++++++-- components/lwp/terminal/tty_internal.h | 25 ++ components/lwp/terminal/tty_ptmx.c | 241 +++++++++++++++++-- components/lwp/utest/Kconfig | 6 +- components/lwp/utest/tty_ptmx_tc.c | 149 ++++++++++++ 6 files changed, 577 insertions(+), 52 deletions(-) diff --git a/components/lwp/terminal/freebsd/tty.c b/components/lwp/terminal/freebsd/tty.c index e5a6e79f13cf..cc0cf5525357 100644 --- a/components/lwp/terminal/freebsd/tty.c +++ b/components/lwp/terminal/freebsd/tty.c @@ -1146,12 +1146,38 @@ static int tty_generic_ioctl(struct lwp_tty *tp, rt_ubase_t cmd, void *data, /* This device supports non-blocking operation. */ return 0; case FIONREAD: - *(int *)data = ttyinq_bytescanonicalized(&tp->t_inq); + { + int nread = ttyinq_bytescanonicalized(&tp->t_inq); + if (lwp_in_user_space((void *)data)) + { + if (lwp_put_to_user(data, &nread, sizeof(int)) != sizeof(int)) + { + return -EFAULT; + } + } + else + { + *(int *)data = nread; + } return 0; + } case FIONWRITE: case TIOCOUTQ: - *(int *)data = ttyoutq_bytesused(&tp->t_outq); + { + int nused = ttyoutq_bytesused(&tp->t_outq); + if (lwp_in_user_space((void *)data)) + { + if (lwp_put_to_user(data, &nused, sizeof(int)) != sizeof(int)) + { + return -EFAULT; + } + } + else + { + *(int *)data = nused; + } return 0; + } #if BSD_USING_FIO_OWNERSHIP case FIOSETOWN: if (tp->t_session != NULL && !tty_is_ctty(tp, td->lwp)) @@ -1269,21 +1295,50 @@ static int tty_generic_ioctl(struct lwp_tty *tp, rt_ubase_t cmd, void *data, *(int *)data = TTYDISC; return 0; case TIOCGPGRP: + { + int pgrp; if (!tty_is_ctty(tp, td->lwp)) + { return -ENOTTY; + } - if (tp->t_pgrp != NULL) - *(int *)data = tp->t_pgrp->pgid; + pgrp = (tp->t_pgrp != NULL) ? tp->t_pgrp->pgid : NO_PID; + if (lwp_in_user_space((void *)data)) + { + if (lwp_put_to_user(data, &pgrp, sizeof(int)) != sizeof(int)) + { + return -EFAULT; + } + } else - *(int *)data = NO_PID; + { + *(int *)data = pgrp; + } return 0; + } case TIOCGSID: + { + int sid; if (!tty_is_ctty(tp, td->lwp)) + { return -ENOTTY; + } MPASS(tp->t_session); - *(int *)data = tp->t_session->sid; + sid = tp->t_session->sid; + if (lwp_in_user_space((void *)data)) + { + if (lwp_put_to_user(data, &sid, sizeof(int)) != sizeof(int)) + { + return -EFAULT; + } + } + else + { + *(int *)data = sid; + } return 0; + } case TIOCNOTTY: return tty_drop_ctty(tp, td->lwp); case TIOCSCTTY: @@ -1338,11 +1393,33 @@ static int tty_generic_ioctl(struct lwp_tty *tp, rt_ubase_t cmd, void *data, return (error); case TIOCGWINSZ: /* Obtain window size. */ - *(struct winsize *)data = tp->t_winsize; + if (lwp_in_user_space((void *)data)) + { + if (lwp_put_to_user(data, &tp->t_winsize, sizeof(struct winsize)) != sizeof(struct winsize)) + { + return -EFAULT; + } + } + else + { + *(struct winsize *)data = tp->t_winsize; + } return 0; case TIOCSWINSZ: /* Set window size. */ - tty_set_winsize(tp, data); + if (lwp_in_user_space((void *)data)) + { + struct winsize ws; + if (lwp_get_from_user(&ws, data, sizeof(struct winsize)) != sizeof(struct winsize)) + { + return -EFAULT; + } + tty_set_winsize(tp, &ws); + } + else + { + tty_set_winsize(tp, data); + } return 0; case TIOCEXCL: tp->t_flags |= TF_EXCLUDE; diff --git a/components/lwp/terminal/freebsd/tty_compat.c b/components/lwp/terminal/freebsd/tty_compat.c index 08ff49f8017e..6fdd399a6eaf 100644 --- a/components/lwp/terminal/freebsd/tty_compat.c +++ b/components/lwp/terminal/freebsd/tty_compat.c @@ -507,45 +507,122 @@ int lwp_tty_ioctl_adapter(lwp_tty_t tp, int cmd, int oflags, void *args, rt_thre /* Controlling terminal */ case TIOCSCTTY: case TIOCNOTTY: + error = IOCTL(cmd, RT_NULL, fflags, td); + break; /* Process group and session ID */ case TIOCGPGRP: + { + int pgrp; + error = IOCTL(TIOCGPGRP, (rt_caddr_t)&pgrp, fflags, td); + if (!error) + { + error = _copy_to_user(args, &pgrp, sizeof(pgrp)); + } + break; + } + case TIOCSPGRP: + { + int pgrp; + error = _copy_from_user(&pgrp, args, sizeof(pgrp)); + if (!error) + { + error = IOCTL(TIOCSPGRP, (rt_caddr_t)&pgrp, fflags, td); + } + break; + } + case TIOCGSID: + { + int sid; + error = IOCTL(TIOCGSID, (rt_caddr_t)&sid, fflags, td); + if (!error) + { + error = _copy_to_user(args, &sid, sizeof(sid)); + } + break; + } - /* TIOCOUTQ */ - /* TIOCSTI */ case TIOCGWINSZ: + { + struct winsize ws; + error = IOCTL(TIOCGWINSZ, (rt_caddr_t)&ws, fflags, td); + if (!error) + { + error = _copy_to_user(args, &ws, sizeof(ws)); + } + break; + } + case TIOCSWINSZ: - error = IOCTL(cmd, (rt_caddr_t)args, fflags, td); + { + struct winsize ws; + error = _copy_from_user(&ws, args, sizeof(ws)); + if (!error) + { + error = IOCTL(TIOCSWINSZ, (rt_caddr_t)&ws, fflags, td); + } break; -#ifdef USING_BSD_IOCTL_EXT - case TIOCMGET: - args->cmd = TIOCMGET; - error = (sys_ioctl(td, (struct ioctl_args *)args)); + } + + case TIOCSTI: + { + char ch; + error = _copy_from_user(&ch, args, sizeof(ch)); + if (!error) + { + error = IOCTL(TIOCSTI, (rt_caddr_t)&ch, fflags, td); + } break; + } - case TIOCMBIS: - args->cmd = TIOCMBIS; - error = (sys_ioctl(td, (struct ioctl_args *)args)); + case TIOCOUTQ: + case FIONWRITE: + { + int nout; + error = IOCTL(cmd, (rt_caddr_t)&nout, fflags, td); + if (!error) + { + error = _copy_to_user(args, &nout, sizeof(nout)); + } break; + } - case TIOCMBIC: - args->cmd = TIOCMBIC; - error = (sys_ioctl(td, (struct ioctl_args *)args)); + case TIOCMGET: + { + int bits; + error = IOCTL(TIOCMGET, (rt_caddr_t)&bits, fflags, td); + if (!error) + { + error = _copy_to_user(args, &bits, sizeof(bits)); + } break; + } + case TIOCMBIS: + case TIOCMBIC: case TIOCMSET: - args->cmd = TIOCMSET; - error = (sys_ioctl(td, (struct ioctl_args *)args)); + { + int bits; + error = _copy_from_user(&bits, args, sizeof(bits)); + if (!error) + { + error = IOCTL(cmd, (rt_caddr_t)&bits, fflags, td); + } break; -#endif /* USING_BSD_IOCTL_EXT */ - /* TIOCGSOFTCAR */ - /* TIOCSSOFTCAR */ + } case FIONREAD: /* TIOCINQ */ - error = (IOCTL(FIONREAD, args, fflags, td)); + { + int nread; + error = IOCTL(FIONREAD, (rt_caddr_t)&nread, fflags, td); + if (!error) + { + error = _copy_to_user(args, &nread, sizeof(nread)); + } break; + } #ifdef USING_BSD_IOCTL_EXT /* TIOCLINUX */ diff --git a/components/lwp/terminal/tty_internal.h b/components/lwp/terminal/tty_internal.h index 8bf336cb89a8..da151f02c051 100644 --- a/components/lwp/terminal/tty_internal.h +++ b/components/lwp/terminal/tty_internal.h @@ -17,6 +17,31 @@ extern struct cdevsw bsd_ttydev_methods; extern struct bsd_fileops bsd_ptsdev_methods; +enum lwp_ptmx_ioctl_arg_type +{ + LWP_PTMX_IOCTL_UNSUPPORTED, + LWP_PTMX_IOCTL_NONE, + LWP_PTMX_IOCTL_INT_IN, + LWP_PTMX_IOCTL_INT_OUT, + LWP_PTMX_IOCTL_UINT_OUT, + LWP_PTMX_IOCTL_CHAR_IN, + LWP_PTMX_IOCTL_WINSIZE_IN, + LWP_PTMX_IOCTL_WINSIZE_OUT, + LWP_PTMX_IOCTL_TERMIOS_IN, + LWP_PTMX_IOCTL_TERMIOS_OUT, +}; + +enum lwp_ptmx_ioctl_arg_type lwp_ptmx_ioctl_arg_type(rt_ubase_t cmd); + +typedef rt_size_t (*lwp_ptmx_copy_fn)(void *dst, void *src, rt_size_t size, + void *context); +typedef int (*lwp_ptmx_ioctl_fn)(rt_ubase_t cmd, void *arg, void *context); + +int lwp_ptmx_ioctl_forward(rt_ubase_t cmd, void *arg, + lwp_ptmx_copy_fn copy_from_user, + lwp_ptmx_copy_fn copy_to_user, + lwp_ptmx_ioctl_fn ioctl, void *context); + /* bsd devsw porting */ void bsd_devsw_init(struct lwp_ttydevsw *tsw); diff --git a/components/lwp/terminal/tty_ptmx.c b/components/lwp/terminal/tty_ptmx.c index 5938bf010390..36b82510ced4 100644 --- a/components/lwp/terminal/tty_ptmx.c +++ b/components/lwp/terminal/tty_ptmx.c @@ -38,7 +38,9 @@ static int ptm_fops_open(struct dfs_file *file) /* detached operation from devfs */ if (rc == 0) + { file->vnode->fops = &ptm_fops; + } } else { @@ -157,6 +159,196 @@ static ssize_t ptm_fops_write(struct dfs_file *file, const void *buf, return rc; } +enum lwp_ptmx_ioctl_arg_type lwp_ptmx_ioctl_arg_type(rt_ubase_t cmd) +{ + switch (cmd) + { + case TIOCSDTR: + case TIOCCDTR: + case TIOCNOTTY: + case TIOCSCTTY: + case TIOCDRAIN: + case TIOCEXCL: + case TIOCNXCL: + case TIOCSTOP: + case TIOCSTART: + case TIOCSTAT: + case TIOCSBRK: + case TIOCCBRK: + return LWP_PTMX_IOCTL_NONE; + + case TIOCPKT: + case TIOCSIG: + case FIONBIO: + case TIOCSPGRP: + case TIOCMSET: + case TIOCMBIS: + case TIOCMBIC: + case FIOASYNC: + case FIOSETOWN: + case TIOCFLUSH: + case TIOCSDRAINWAIT: + case TIOCCONS: + case TIOCSETD: + return LWP_PTMX_IOCTL_INT_IN; + + case TIOCGPGRP: + case TIOCGSID: + case FIONREAD: + case TIOCOUTQ: + case FIONWRITE: + case TIOCMGET: + case FIOGETOWN: + case TIOCGETD: + case TIOCGDRAINWAIT: + return LWP_PTMX_IOCTL_INT_OUT; + + case TIOCGPTN: + return LWP_PTMX_IOCTL_UINT_OUT; + + case TIOCSTI: + return LWP_PTMX_IOCTL_CHAR_IN; + + case TIOCSWINSZ: + return LWP_PTMX_IOCTL_WINSIZE_IN; + + case TIOCGWINSZ: + return LWP_PTMX_IOCTL_WINSIZE_OUT; + + case TCSETS: + case TCSETSW: + case TCSETSF: + return LWP_PTMX_IOCTL_TERMIOS_IN; + + case TCGETS: + return LWP_PTMX_IOCTL_TERMIOS_OUT; + + default: + return LWP_PTMX_IOCTL_UNSUPPORTED; + } +} + +int lwp_ptmx_ioctl_forward(rt_ubase_t cmd, void *arg, + lwp_ptmx_copy_fn copy_from_user, + lwp_ptmx_copy_fn copy_to_user, + lwp_ptmx_ioctl_fn ioctl, void *context) +{ + int rc; + rt_size_t arg_size = 0; + rt_bool_t copy_out = RT_FALSE; + void *kernel_arg = RT_NULL; + enum lwp_ptmx_ioctl_arg_type arg_type; + union + { + int int_value; + unsigned int uint_value; + char char_value; + struct winsize winsize_value; + struct termios termios_value; + } value; + + arg_type = lwp_ptmx_ioctl_arg_type(cmd); + switch (arg_type) + { + case LWP_PTMX_IOCTL_NONE: + break; + case LWP_PTMX_IOCTL_INT_IN: + kernel_arg = &value.int_value; + arg_size = sizeof(value.int_value); + break; + case LWP_PTMX_IOCTL_INT_OUT: + kernel_arg = &value.int_value; + arg_size = sizeof(value.int_value); + copy_out = RT_TRUE; + break; + case LWP_PTMX_IOCTL_UINT_OUT: + kernel_arg = &value.uint_value; + arg_size = sizeof(value.uint_value); + copy_out = RT_TRUE; + break; + case LWP_PTMX_IOCTL_CHAR_IN: + kernel_arg = &value.char_value; + arg_size = sizeof(value.char_value); + break; + case LWP_PTMX_IOCTL_WINSIZE_IN: + kernel_arg = &value.winsize_value; + arg_size = sizeof(value.winsize_value); + break; + case LWP_PTMX_IOCTL_WINSIZE_OUT: + kernel_arg = &value.winsize_value; + arg_size = sizeof(value.winsize_value); + copy_out = RT_TRUE; + break; + case LWP_PTMX_IOCTL_TERMIOS_IN: + kernel_arg = &value.termios_value; + arg_size = sizeof(value.termios_value); + break; + case LWP_PTMX_IOCTL_TERMIOS_OUT: + kernel_arg = &value.termios_value; + arg_size = sizeof(value.termios_value); + copy_out = RT_TRUE; + break; + default: + return -ENOTTY; + } + + if (arg_size && !copy_out && + copy_from_user(kernel_arg, arg, arg_size, context) != arg_size) + { + return -EFAULT; + } + + rc = ioctl(cmd, kernel_arg, context); + if (!rc && copy_out && + copy_to_user(arg, kernel_arg, arg_size, context) != arg_size) + { + rc = -EFAULT; + } + + return rc; +} + +struct ptm_ioctl_context +{ + struct dfs_file *file; + lwp_tty_t tp; +}; + +static rt_size_t ptm_copy_from_user(void *dst, void *src, rt_size_t size, + void *context) +{ + RT_UNUSED(context); + return lwp_get_from_user(dst, src, size); +} + +static rt_size_t ptm_copy_to_user(void *dst, void *src, rt_size_t size, + void *context) +{ + RT_UNUSED(context); + return lwp_put_to_user(dst, src, size); +} + +static int ptm_ioctl_backend(rt_ubase_t cmd, void *arg, void *context) +{ + struct ptm_ioctl_context *ptm_context = context; + + return bsd_ptsdev_methods.fo_ioctl( + ptm_context->tp, cmd, arg, 0, FFLAGS(ptm_context->file->flags), + rt_thread_self()); +} + +static int ptm_ioctl_forward(struct dfs_file *file, lwp_tty_t tp, + rt_ubase_t cmd, void *arg) +{ + struct ptm_ioctl_context context = { + .file = file, + .tp = tp, + }; + + return lwp_ptmx_ioctl_forward(cmd, arg, ptm_copy_from_user, + ptm_copy_to_user, ptm_ioctl_backend, &context); +} + static int ptm_fops_ioctl(struct dfs_file *file, int cmd, void *arg) { int rc; @@ -171,35 +363,40 @@ static int ptm_fops_ioctl(struct dfs_file *file, int cmd, void *arg) switch (cmd_normal) { - case TIOCSPTLCK: + case TIOCSPTLCK: + { + int is_lock; + if (lwp_get_from_user(&is_lock, arg, sizeof(int)) != sizeof(int)) { - int is_lock; - if (lwp_get_from_user(&is_lock, arg, sizeof(int)) != sizeof(int)) - return -EFAULT; - pts_set_lock(tp, is_lock); - rc = 0; + return -EFAULT; } - break; - case TIOCGPTLCK: + pts_set_lock(tp, is_lock); + rc = 0; + } + break; + case TIOCGPTLCK: + { + int is_lock = pts_is_locked(tp); + if (lwp_put_to_user(arg, &is_lock, sizeof(int)) != sizeof(int)) { - int is_lock = pts_is_locked(tp); - if (lwp_put_to_user(arg, &is_lock, sizeof(int)) != sizeof(int)) - return -EFAULT; - rc = 0; + return -EFAULT; } - break; - case TIOCGPKT: + rc = 0; + } + break; + case TIOCGPKT: + { + int pktmode = pts_get_pktmode(tp); + if (lwp_put_to_user(arg, &pktmode, sizeof(int)) != sizeof(int)) { - int pktmode = pts_get_pktmode(tp); - if (lwp_put_to_user(arg, &pktmode, sizeof(int)) != sizeof(int)) - return -EFAULT; - rc = 0; + return -EFAULT; } + rc = 0; + } + break; + default: + rc = ptm_ioctl_forward(file, tp, cmd_normal, arg); break; - default: - rc = bsd_ptsdev_methods.fo_ioctl( - tp, cmd_normal, arg, 0, FFLAGS(file->flags), rt_thread_self()); - break; } } else diff --git a/components/lwp/utest/Kconfig b/components/lwp/utest/Kconfig index 2901f8f86a4d..7bdcfefbea41 100644 --- a/components/lwp/utest/Kconfig +++ b/components/lwp/utest/Kconfig @@ -6,11 +6,11 @@ config RT_UTEST_LWP default n config RT_UTEST_LWP_TTY_PTMX - bool "Enable Utest for tty_ptmx buffer overflow regression (V-004)" + bool "Enable tty_ptmx regression tests" depends on RT_USING_SMART default n help - Regression guard for the snprintf buffer bounds fix in - components/lwp/terminal/tty_ptmx.c. + Regression guards for tty_ptmx buffer bounds and ioctl argument + handling. endmenu diff --git a/components/lwp/utest/tty_ptmx_tc.c b/components/lwp/utest/tty_ptmx_tc.c index 74416bf5ef1e..f8dc8c955396 100644 --- a/components/lwp/utest/tty_ptmx_tc.c +++ b/components/lwp/utest/tty_ptmx_tc.c @@ -38,6 +38,7 @@ #include "utest_assert.h" +#include "../terminal/tty_internal.h" #include #include #include @@ -45,6 +46,71 @@ #define DEV_REL_PATH "/ptmx" #define CANARY 0xAB +struct tty_ptmx_forward_state +{ + int copy_from_calls; + int copy_to_calls; + int ioctl_calls; + int fail_copy_from; + int fail_copy_to; + int output_value; + int forwarded_value; + rt_size_t copy_size; + rt_ubase_t forwarded_cmd; + void *forwarded_arg; +}; + +static rt_size_t tty_ptmx_copy_from(void *dst, void *src, rt_size_t size, + void *context) +{ + struct tty_ptmx_forward_state *state = context; + + state->copy_from_calls++; + state->copy_size = size; + if (state->fail_copy_from) + { + return 0; + } + memcpy(dst, src, size); + return size; +} + +static rt_size_t tty_ptmx_copy_to(void *dst, void *src, rt_size_t size, + void *context) +{ + struct tty_ptmx_forward_state *state = context; + + state->copy_to_calls++; + state->copy_size = size; + if (state->fail_copy_to) + { + return 0; + } + memcpy(dst, src, size); + return size; +} + +static int tty_ptmx_forward_ioctl(rt_ubase_t cmd, void *arg, void *context) +{ + struct tty_ptmx_forward_state *state = context; + + state->ioctl_calls++; + state->forwarded_cmd = cmd; + state->forwarded_arg = arg; + if (arg) + { + if (cmd == TIOCMGET) + { + *(int *)arg = state->output_value; + } + else + { + state->forwarded_value = *(int *)arg; + } + } + return 0; +} + static void tty_ptmx_buffer_bounds_tc(void) { /* Simulate the allocation and snprintf call from lwp_ptmx_init(). */ @@ -89,6 +155,87 @@ static void tty_ptmx_buffer_bounds_tc(void) } } +static void tty_ptmx_ioctl_arg_type_tc(void) +{ + uassert_int_equal(lwp_ptmx_ioctl_arg_type(TIOCSPGRP), LWP_PTMX_IOCTL_INT_IN); + uassert_int_equal(lwp_ptmx_ioctl_arg_type(TIOCMGET), LWP_PTMX_IOCTL_INT_OUT); + uassert_int_equal(lwp_ptmx_ioctl_arg_type(TIOCSTI), LWP_PTMX_IOCTL_CHAR_IN); + uassert_int_equal(lwp_ptmx_ioctl_arg_type(TIOCEXCL), LWP_PTMX_IOCTL_NONE); + uassert_int_equal(lwp_ptmx_ioctl_arg_type(0xffffffffU), LWP_PTMX_IOCTL_UNSUPPORTED); +} + +static void tty_ptmx_ioctl_forward_tc(void) +{ + struct tty_ptmx_forward_state state; + int value; + int rc; + + memset(&state, 0, sizeof(state)); + value = 73; + rc = lwp_ptmx_ioctl_forward(TIOCSPGRP, &value, tty_ptmx_copy_from, + tty_ptmx_copy_to, tty_ptmx_forward_ioctl, &state); + uassert_int_equal(rc, 0); + uassert_int_equal(state.copy_from_calls, 1); + uassert_int_equal(state.copy_to_calls, 0); + uassert_int_equal(state.ioctl_calls, 1); + uassert_int_equal(state.copy_size, sizeof(value)); + uassert_int_equal(state.forwarded_cmd, TIOCSPGRP); + uassert_int_equal(state.forwarded_value, value); + + memset(&state, 0, sizeof(state)); + state.output_value = 41; + value = 0; + rc = lwp_ptmx_ioctl_forward(TIOCMGET, &value, tty_ptmx_copy_from, + tty_ptmx_copy_to, tty_ptmx_forward_ioctl, &state); + uassert_int_equal(rc, 0); + uassert_int_equal(state.copy_from_calls, 0); + uassert_int_equal(state.copy_to_calls, 1); + uassert_int_equal(state.ioctl_calls, 1); + uassert_int_equal(value, state.output_value); + + memset(&state, 0, sizeof(state)); + value = 1; + rc = lwp_ptmx_ioctl_forward(FIONBIO, &value, tty_ptmx_copy_from, + tty_ptmx_copy_to, tty_ptmx_forward_ioctl, &state); + uassert_int_equal(rc, 0); + uassert_int_equal(state.copy_from_calls, 1); + uassert_int_equal(state.forwarded_value, value); + + memset(&state, 0, sizeof(state)); + rc = lwp_ptmx_ioctl_forward(TIOCEXCL, RT_NULL, tty_ptmx_copy_from, + tty_ptmx_copy_to, tty_ptmx_forward_ioctl, &state); + uassert_int_equal(rc, 0); + uassert_int_equal(state.copy_from_calls, 0); + uassert_int_equal(state.copy_to_calls, 0); + uassert_int_equal(state.ioctl_calls, 1); + uassert_null(state.forwarded_arg); + + memset(&state, 0, sizeof(state)); + state.fail_copy_from = 1; + value = 73; + rc = lwp_ptmx_ioctl_forward(TIOCSPGRP, &value, tty_ptmx_copy_from, + tty_ptmx_copy_to, tty_ptmx_forward_ioctl, &state); + uassert_int_equal(rc, -EFAULT); + uassert_int_equal(state.ioctl_calls, 0); + + memset(&state, 0, sizeof(state)); + state.fail_copy_to = 1; + state.output_value = 41; + rc = lwp_ptmx_ioctl_forward(TIOCMGET, &value, tty_ptmx_copy_from, + tty_ptmx_copy_to, tty_ptmx_forward_ioctl, &state); + uassert_int_equal(rc, -EFAULT); + uassert_int_equal(state.copy_to_calls, 1); + uassert_int_equal(state.ioctl_calls, 1); + + memset(&state, 0, sizeof(state)); + rc = lwp_ptmx_ioctl_forward(0xffffffffU, &value, tty_ptmx_copy_from, + tty_ptmx_copy_to, tty_ptmx_forward_ioctl, &state); + uassert_int_equal(rc, -ENOTTY); + uassert_int_equal(state.copy_from_calls, 0); + uassert_int_equal(state.copy_to_calls, 0); + uassert_int_equal(state.ioctl_calls, 0); +} + static rt_err_t utest_tc_init(void) { return RT_EOK; @@ -102,5 +249,7 @@ static rt_err_t utest_tc_cleanup(void) static void testcase(void) { UTEST_UNIT_RUN(tty_ptmx_buffer_bounds_tc); + UTEST_UNIT_RUN(tty_ptmx_ioctl_arg_type_tc); + UTEST_UNIT_RUN(tty_ptmx_ioctl_forward_tc); } UTEST_TC_EXPORT(testcase, "testcases.lwp.tty_ptmx.buffer_bounds", utest_tc_init, utest_tc_cleanup, 10);