MFC r207329, r208716:

- Extract the IODEV_PIO interface from ia64 and make it MI.
- On i386 and amd64 the old behaviour is kept but multithreaded
  processes must use the new interface in order to work well.
- Support for the other architectures is greatly improved.

Sponsored by:	Sandvine Incorporated

Approved by:	re (kib, bz)
This commit is contained in:
Attilio Rao 2010-06-01 21:19:58 +00:00
parent af89e29624
commit 875e0aa40d
9 changed files with 325 additions and 215 deletions

View File

@ -27,7 +27,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd February 8, 2010
.Dd June 01, 2010
.Dt IO 4
.Os
.Sh NAME
@ -35,32 +35,89 @@
.Nd I/O privilege file
.Sh SYNOPSIS
.Cd "device io"
.Pp
.In sys/types.h
.In sys/ioctl.h
.In dev/io/iodev.h
.In machine/iodev.h
.Pp
.Bd -literal
struct iodev_pio_req {
u_int access;
u_int port;
u_int width;
u_int val;
};
.Sh DESCRIPTION
The special file
.Pa /dev/io
is a controlled security hole that allows a process to gain I/O
privileges
(which are normally reserved for kernel-internal code).
Any process that holds a file descriptor on
.Pa /dev/io
open will get its
.Em IOPL
bits in the flag register set, thus allowing it to perform direct
I/O operations.
This can be useful in order to write userland
programs that handle some hardware directly.
Note that even read-only access will grant the full I/O privileges.
.Pp
The usual operations on the device are to open it via the
.Xr open 2
interface and to send I/O requests to the file descriptor using the
.Xr ioctl 2
syscall.
.Pp
The
.Xr ioctl 2
requests available for
.Pa /dev/io
are mostly platform dependent, but there are also some in common between
all of them.
The
.Dv IODEV_PIO
is used by all the architectures in order to request that an I/O operation
be performed. It takes a 'struct iodev_pio_req' argument
that must be previously setup.
.Pp
The
.Fa access
member specifies the type of operation requested. It may be:
.Bl -tag -width IODEV_PIO_WRITE
.It Dv IODEV_PIO_READ
The operation is an "in" type. A value will be read from the specified port
(retrieved from the
.Fa port
member) and the result will be stored in the
.Fa val
member.
.It Dv IODEV_PIO_WRITE
The operation is a "out" type. The value will be fetched from the
.Fa val
member and will be written out to the specified port (defined as the
.Fa port
member).
.El
.Pp
Finally, the
.Fa width
member specifies the size of the operand to be read/written, expressed
in bytes.
.Pp
In addition to any file access permissions on
.Pa /dev/io ,
the kernel enforces that only the super-user may open this device.
.Sh FILES
.Bl -tag -width Pa -compact
.It Pa /dev/io
.El
.Sh LEGACY
The
.Pa /dev/io
interface used to be very i386 specific and worked differently. The initial
implementation, in fact, simply raised the
.Em IOPL
of the current thread when
.Xr open 2
was called on the file. This behaviour is retained in the current
implementation as legacy support for both i386 and amd64 architectures.
.Sh SEE ALSO
.Xr close 2 ,
.Xr i386_get_ioperm 2 ,
.Xr i386_set_ioperm 2 ,
.Xr ioctl 2 ,
.Xr open 2 ,
.Xr mem 4
.Sh HISTORY
The

View File

@ -28,60 +28,32 @@
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/signalvar.h>
#include <sys/systm.h>
#include <machine/db_machdep.h>
#include <machine/frame.h>
#include <machine/psl.h>
#include <machine/specialreg.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <machine/iodev.h>
#include <machine/psl.h>
/* ARGSUSED */
int
ioopen(struct cdev *dev __unused, int flags __unused, int fmt __unused,
struct thread *td)
iodev_open(struct thread *td)
{
int error;
error = priv_check(td, PRIV_IO);
if (error != 0)
return (error);
error = securelevel_gt(td->td_ucred, 0);
if (error != 0)
return (error);
td->td_frame->tf_rflags |= PSL_IOPL;
return (0);
}
/* ARGSUSED */
int
ioclose(struct cdev *dev __unused, int flags __unused, int fmt __unused,
struct thread *td)
iodev_close(struct thread *td)
{
td->td_frame->tf_rflags &= ~PSL_IOPL;
return (0);
}
/* ARGSUSED */
int
ioioctl(struct cdev *dev __unused, u_long cmd __unused, caddr_t data __unused,
int fflag __unused, struct thread *td __unused)
iodev_ioctl(u_long cmd __unused, caddr_t data __unused)
{
return (ENXIO);
return (ENOIOCTL);
}

View File

@ -25,7 +25,22 @@
*
* $FreeBSD$
*/
#ifndef _MACHINE_IODEV_H_
#define _MACHINE_IODEV_H_
d_open_t ioopen;
d_close_t ioclose;
d_ioctl_t ioioctl;
#ifdef _KERNEL
#include <machine/cpufunc.h>
#define iodev_read_1 inb
#define iodev_read_2 inw
#define iodev_read_4 inl
#define iodev_write_1 outb
#define iodev_write_2 outw
#define iodev_write_4 outl
int iodev_open(struct thread *td);
int iodev_close(struct thread *td);
int iodev_ioctl(u_long cmd, caddr_t data);
#endif /* _KERNEL */
#endif /* _MACHINE_IODEV_H_ */

View File

@ -30,22 +30,27 @@ __FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/ioccom.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/signalvar.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <machine/iodev.h>
#include <dev/io/iodev.h>
static int ioopen(struct cdev *dev, int flags, int fmt,
struct thread *td);
static int ioclose(struct cdev *dev, int flags, int fmt,
struct thread *td);
static int ioioctl(struct cdev *dev, u_long cmd, caddr_t data,
int fflag, struct thread *td);
static int iopio_read(struct iodev_pio_req *req);
static int iopio_write(struct iodev_pio_req *req);
static struct cdev *iodev;
static struct cdevsw io_cdevsw = {
@ -56,6 +61,129 @@ static struct cdevsw io_cdevsw = {
.d_name = "io",
};
/* ARGSUSED */
static int
ioopen(struct cdev *dev __unused, int flags __unused, int fmt __unused,
struct thread *td)
{
int error;
error = priv_check(td, PRIV_IO);
if (error != 0)
return (error);
error = securelevel_gt(td->td_ucred, 0);
if (error != 0)
return (error);
error = iodev_open(td);
return (error);
}
/* ARGSUSED */
static int
ioclose(struct cdev *dev __unused, int flags __unused, int fmt __unused,
struct thread *td)
{
return (iodev_close(td));
}
/* ARGSUSED */
static int
ioioctl(struct cdev *dev __unused, u_long cmd, caddr_t data,
int fflag __unused, struct thread *td __unused)
{
struct iodev_pio_req *pio_req;
int error;
switch (cmd) {
case IODEV_PIO:
pio_req = (struct iodev_pio_req *)data;
switch (pio_req->access) {
case IODEV_PIO_READ:
error = iopio_read(pio_req);
break;
case IODEV_PIO_WRITE:
error = iopio_write(pio_req);
break;
default:
error = EINVAL;
break;
}
break;
default:
error = iodev_ioctl(cmd, data);
}
return (error);
}
static int
iopio_read(struct iodev_pio_req *req)
{
switch (req->width) {
case 1:
req->val = iodev_read_1(req->port);
break;
case 2:
if (req->port & 1) {
req->val = iodev_read_1(req->port);
req->val |= iodev_read_1(req->port + 1) << 8;
} else
req->val = iodev_read_2(req->port);
break;
case 4:
if (req->port & 1) {
req->val = iodev_read_1(req->port);
req->val |= iodev_read_2(req->port + 1) << 8;
req->val |= iodev_read_1(req->port + 3) << 24;
} else if (req->port & 2) {
req->val = iodev_read_2(req->port);
req->val |= iodev_read_2(req->port + 2) << 16;
} else
req->val = iodev_read_4(req->port);
break;
default:
return (EINVAL);
}
return (0);
}
static int
iopio_write(struct iodev_pio_req *req)
{
switch (req->width) {
case 1:
iodev_write_1(req->port, req->val);
break;
case 2:
if (req->port & 1) {
iodev_write_1(req->port, req->val);
iodev_write_1(req->port + 1, req->val >> 8);
} else
iodev_write_2(req->port, req->val);
break;
case 4:
if (req->port & 1) {
iodev_write_1(req->port, req->val);
iodev_write_2(req->port + 1, req->val >> 8);
iodev_write_1(req->port + 3, req->val >> 24);
} else if (req->port & 2) {
iodev_write_2(req->port, req->val);
iodev_write_2(req->port + 2, req->val >> 16);
} else
iodev_write_4(req->port, req->val);
break;
default:
return (EINVAL);
}
return (0);
}
/* ARGSUSED */
static int
io_modevent(module_t mod __unused, int type, void *data __unused)

44
sys/dev/io/iodev.h Normal file
View File

@ -0,0 +1,44 @@
/*-
* Copyright (c) 2010 Marcel Moolenaar
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef _DEV_IODEV_H_
#define _DEV_IODEV_H_
#define IODEV_PIO_READ 0
#define IODEV_PIO_WRITE 1
struct iodev_pio_req {
u_int access;
u_int port;
u_int width;
u_int val;
};
#define IODEV_PIO _IOWR('I', 0, struct iodev_pio_req)
#endif /* _DEV_IODEV_H_ */

View File

@ -28,60 +28,32 @@
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/signalvar.h>
#include <sys/systm.h>
#include <machine/db_machdep.h>
#include <machine/frame.h>
#include <machine/psl.h>
#include <machine/specialreg.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <machine/iodev.h>
#include <machine/psl.h>
/* ARGSUSED */
int
ioopen(struct cdev *dev __unused, int flags __unused, int fmt __unused,
struct thread *td)
iodev_open(struct thread *td)
{
int error;
error = priv_check(td, PRIV_IO);
if (error != 0)
return (error);
error = securelevel_gt(td->td_ucred, 0);
if (error != 0)
return (error);
td->td_frame->tf_eflags |= PSL_IOPL;
return (0);
}
/* ARGSUSED */
int
ioclose(struct cdev *dev __unused, int flags __unused, int fmt __unused,
struct thread *td)
iodev_close(struct thread *td)
{
td->td_frame->tf_eflags &= ~PSL_IOPL;
return (0);
}
/* ARGSUSED */
int
ioioctl(struct cdev *dev __unused, u_long cmd __unused, caddr_t data __unused,
int fflag __unused, struct thread *td __unused)
iodev_ioctl(u_long cmd __unused, caddr_t data __unused)
{
return (ENXIO);
return (ENOIOCTL);
}

View File

@ -25,7 +25,22 @@
*
* $FreeBSD$
*/
#ifndef _MACHINE_IODEV_H_
#define _MACHINE_IODEV_H_
d_open_t ioopen;
d_close_t ioclose;
d_ioctl_t ioioctl;
#ifdef _KERNEL
#include <machine/cpufunc.h>
#define iodev_read_1 inb
#define iodev_read_2 inw
#define iodev_read_4 inl
#define iodev_write_1 outb
#define iodev_write_2 outw
#define iodev_write_4 outl
int iodev_open(struct thread *td);
int iodev_close(struct thread *td);
int iodev_ioctl(u_long cmd, caddr_t data);
#endif /* _KERNEL */
#endif /* _MACHINE_IODEV_H_ */

View File

@ -40,31 +40,13 @@ __FBSDID("$FreeBSD$");
#include <machine/efi.h>
#include <machine/iodev.h>
static int iodev_pio_read(struct iodev_pio_req *req);
static int iodev_pio_write(struct iodev_pio_req *req);
static int iodev_efivar_getvar(struct iodev_efivar_req *req);
static int iodev_efivar_nextname(struct iodev_efivar_req *req);
static int iodev_efivar_setvar(struct iodev_efivar_req *req);
/* ARGSUSED */
int
ioopen(struct cdev *dev __unused, int flags __unused, int fmt __unused,
struct thread *td)
{
int error;
error = priv_check(td, PRIV_IO);
if (error == 0)
error = securelevel_gt(td->td_ucred, 0);
return (error);
}
/* ARGSUSED */
int
ioclose(struct cdev *dev __unused, int flags __unused, int fmt __unused,
struct thread *td __unused)
iodev_open(struct thread *td __unused)
{
return (0);
@ -72,29 +54,19 @@ ioclose(struct cdev *dev __unused, int flags __unused, int fmt __unused,
/* ARGSUSED */
int
ioioctl(struct cdev *dev __unused, u_long cmd, caddr_t data,
int fflag __unused, struct thread *td __unused)
iodev_close(struct thread *td __unused)
{
return (0);
}
int
iodev_ioctl(u_long cmd, caddr_t data)
{
struct iodev_efivar_req *efivar_req;
struct iodev_pio_req *pio_req;
int error;
error = ENOIOCTL;
switch (cmd) {
case IODEV_PIO:
pio_req = (struct iodev_pio_req *)data;
switch (pio_req->access) {
case IODEV_PIO_READ:
error = iodev_pio_read(pio_req);
break;
case IODEV_PIO_WRITE:
error = iodev_pio_write(pio_req);
break;
default:
error = EINVAL;
break;
}
break;
case IODEV_EFIVAR:
efivar_req = (struct iodev_efivar_req *)data;
efivar_req->result = 0; /* So it's well-defined */
@ -113,77 +85,13 @@ ioioctl(struct cdev *dev __unused, u_long cmd, caddr_t data,
break;
}
break;
default:
error = ENOIOCTL;
}
return (error);
}
static int
iodev_pio_read(struct iodev_pio_req *req)
{
switch (req->width) {
case 1:
req->val = bus_space_read_io_1(req->port);
break;
case 2:
if (req->port & 1) {
req->val = bus_space_read_io_1(req->port);
req->val |= bus_space_read_io_1(req->port + 1) << 8;
} else
req->val = bus_space_read_io_2(req->port);
break;
case 4:
if (req->port & 1) {
req->val = bus_space_read_io_1(req->port);
req->val |= bus_space_read_io_2(req->port + 1) << 8;
req->val |= bus_space_read_io_1(req->port + 3) << 24;
} else if (req->port & 2) {
req->val = bus_space_read_io_2(req->port);
req->val |= bus_space_read_io_2(req->port + 2) << 16;
} else
req->val = bus_space_read_io_4(req->port);
break;
default:
return (EINVAL);
}
return (0);
}
static int
iodev_pio_write(struct iodev_pio_req *req)
{
switch (req->width) {
case 1:
bus_space_write_io_1(req->port, req->val);
break;
case 2:
if (req->port & 1) {
bus_space_write_io_1(req->port, req->val);
bus_space_write_io_1(req->port + 1, req->val >> 8);
} else
bus_space_write_io_2(req->port, req->val);
break;
case 4:
if (req->port & 1) {
bus_space_write_io_1(req->port, req->val);
bus_space_write_io_2(req->port + 1, req->val >> 8);
bus_space_write_io_1(req->port + 3, req->val >> 24);
} else if (req->port & 2) {
bus_space_write_io_2(req->port, req->val);
bus_space_write_io_2(req->port + 2, req->val >> 16);
} else
bus_space_write_io_4(req->port, req->val);
break;
default:
return (EINVAL);
}
return (0);
}
static int
iodev_efivar_getvar(struct iodev_efivar_req *req)
{

View File

@ -31,22 +31,16 @@
#include <sys/uuid.h>
struct iodev_pio_req {
u_int access;
#define IODEV_PIO_READ 0
#define IODEV_PIO_WRITE 1
u_int port;
u_int width;
u_int val;
};
#ifdef _KERNEL
#include <machine/bus.h>
#endif
#define IODEV_PIO _IOWR('I', 0, struct iodev_pio_req)
struct iodev_efivar_req {
u_int access;
#define IODEV_EFIVAR_GETVAR 0
#define IODEV_EFIVAR_NEXTNAME 1
#define IODEV_EFIVAR_SETVAR 2
struct iodev_efivar_req {
u_int access;
u_int result; /* errno value */
size_t namesize;
u_short *name; /* UCS-2 */
@ -59,11 +53,16 @@ struct iodev_efivar_req {
#define IODEV_EFIVAR _IOWR('I', 1, struct iodev_efivar_req)
#ifdef _KERNEL
#define iodev_read_1 bus_space_read_io_1
#define iodev_read_2 bus_space_read_io_2
#define iodev_read_4 bus_space_read_io_4
#define iodev_write_1 bus_space_write_io_1
#define iodev_write_2 bus_space_write_io_2
#define iodev_write_4 bus_space_write_io_4
d_open_t ioopen;
d_close_t ioclose;
d_ioctl_t ioioctl;
#endif
int iodev_open(struct thread *td);
int iodev_close(struct thread *td);
int iodev_ioctl(u_long, caddr_t data);
#endif /* _KERNEL */
#endif /* _MACHINE_IODEV_H_ */