Skip to content

Commit

Permalink
preload: Only emulate ioctls on emulated devices
Browse files Browse the repository at this point in the history
Don't invoke the remote ioctl handler for host devices. This fixes e.g.
ioctls on /dev/tty or other standard devices which the tested program
may want to do.

This bug was hidden by the conditional check in t_usbfs_ioctl_static()
which skipped the check if /dev/tty wasn't accessible -- which is the
case in `meson test`. Move to a FIONREAD ioctl on /dev/stdout, which
works on ttys and pipes, i.e. everywhere. Make that check unconditional.
  • Loading branch information
martinpitt committed Jan 2, 2025
1 parent 7870dab commit fe0ec89
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 31 deletions.
3 changes: 1 addition & 2 deletions src/ioctl.vapi
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace Ioctl {
[CCode (cheader_filename = "sys/ioctl.h")]
public const int USBDEVFS_RESETEP;
[CCode (cheader_filename = "sys/ioctl.h")]
public const int TIOCSBRK;
public const int FIONREAD;

[CCode (cheader_filename = "linux/usbdevice_fs.h")]
public const int USBDEVFS_CAP_ZERO_PACKET;
Expand Down Expand Up @@ -144,4 +144,3 @@ namespace Ioctl {
uint8 data[0];
}
}

33 changes: 14 additions & 19 deletions src/libumockdev-preload.c
Original file line number Diff line number Diff line change
Expand Up @@ -502,16 +502,16 @@ static fd_map ioctl_wrapped_fds;
struct ioctl_fd_info {
char *dev_path;
int ioctl_sock;
int is_default;
bool is_emulated;
pthread_mutex_t sock_lock;
};

static void
ioctl_emulate_open(int fd, const char *dev_path, int must_exist)
ioctl_emulate_open(int fd, const char *dev_path, bool is_emulated)
{
libc_func(socket, int, int, int, int);
libc_func(connect, int, int, const struct sockaddr *, socklen_t);
int is_default = 0;
bool is_default = false;
int sock;
int ret;
struct ioctl_fd_info *fdinfo;
Expand All @@ -525,27 +525,22 @@ ioctl_emulate_open(int fd, const char *dev_path, int must_exist)

if (path_exists (addr.sun_path) != 0) {
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/ioctl/_default", getenv("UMOCKDEV_DIR"));
is_default = 1;
is_default = true;
}

int orig_errno = errno;
sock = _socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
if (must_exist) {
fprintf(stderr, "ERROR: libumockdev-preload: Failed to open ioctl socket for %s",
dev_path);
exit(1);
} else {
errno = orig_errno;
return;
}
DBG(DBG_IOCTL, "ioctl_emulate_open fd %i (%s): not emulated\n", fd, dev_path);
errno = orig_errno;
return;
}

ret = _connect(sock, (const struct sockaddr *) &addr, sizeof(addr));
if (ret == -1) {
if (must_exist) {
fprintf(stderr, "ERROR: libumockdev-preload: Failed to connect to ioctl socket for %s",
dev_path);
if (!is_default || errno != ENOENT) {
fprintf(stderr, "ERROR: libumockdev-preload: Failed to connect to ioctl socket %s for %s: %m\n",
addr.sun_path, dev_path);
exit(1);
} else {
errno = orig_errno;
Expand All @@ -554,13 +549,13 @@ ioctl_emulate_open(int fd, const char *dev_path, int must_exist)
}

fdinfo = mallocx(sizeof(struct ioctl_fd_info));
fdinfo->is_emulated = is_emulated;
fdinfo->ioctl_sock = sock;
fdinfo->dev_path = strdupx(dev_path);
fdinfo->is_default = is_default;
pthread_mutex_init(&fdinfo->sock_lock, NULL);

fd_map_add(&ioctl_wrapped_fds, fd, fdinfo);
DBG(DBG_IOCTL, "ioctl_emulate_open fd %i (%s): connected ioctl sockert\n", fd, dev_path);
DBG(DBG_IOCTL, "ioctl_emulate_open fd %i (%s): connected ioctl socket\n", fd, dev_path);
errno = orig_errno;
}

Expand Down Expand Up @@ -625,8 +620,8 @@ remote_emulate(int fd, int cmd, long arg1, long arg2)
}
IOCTL_UNLOCK;

/* Only pass on ioctl requests for the default handler. */
if (fdinfo->is_default && cmd != IOCTL_REQ_IOCTL) {
/* Only pass on ioctl requests for emulated devices */
if (!fdinfo->is_emulated) {
pthread_sigmask(SIG_SETMASK, &sig_restore, NULL);
return UNHANDLED;
}
Expand Down
19 changes: 9 additions & 10 deletions tests/test-umockdev-vala.vala
Original file line number Diff line number Diff line change
Expand Up @@ -269,23 +269,22 @@ E: SUBSYSTEM=usb
/* no ioctl tree loaded */
var ci = Ioctl.usbdevfs_connectinfo();
assert_cmpint (Posix.ioctl (fd, Ioctl.USBDEVFS_CONNECTINFO, ref ci), CompareOperator.EQ, -1);
// usually ENOTTY, but seem to be EINVAL
assert_cmpint (Posix.errno, CompareOperator.GE, 22);
assert_cmpint (Posix.errno, CompareOperator.GE, Posix.EINVAL);
Posix.errno = 0;

// unknown ioctls don't work on an emulated device
assert_cmpint (Posix.ioctl (fd, Ioctl.TIOCSBRK, 0), CompareOperator.EQ, -1);
int argp;
assert_cmpint (Posix.ioctl (fd, Ioctl.FIONREAD, &argp), CompareOperator.EQ, -1);
assert_cmpint (Posix.errno, CompareOperator.EQ, Posix.ENOTTY);
Posix.close (fd);
Posix.errno = 0;

// unknown ioctls do work on non-emulated devices
int fd2 = Posix.open ("/dev/tty", Posix.O_RDWR, 0);
if (fd2 > 0) {
assert_cmpint (Posix.ioctl (fd2, Ioctl.TIOCSBRK, 0), CompareOperator.EQ, 0);
assert_cmpint (Posix.errno, CompareOperator.EQ, 0);
Posix.close (fd2);
}

fd = Posix.open ("/dev/stdout", Posix.O_WRONLY, 0);
assert_cmpint (fd, CompareOperator.GE, 0);
assert_cmpint (Posix.errno, CompareOperator.EQ, 0);
assert_cmpint (Posix.ioctl (fd, Ioctl.FIONREAD, out argp), CompareOperator.EQ, 0);
assert_cmpint (Posix.errno, CompareOperator.EQ, 0);
Posix.close (fd);
}

Expand Down

0 comments on commit fe0ec89

Please sign in to comment.