Found the solution here.
What you want to do is write a trusted I/O enabling program in C that allows access to only the desired ports, then uses execvp() to execute your script at the caller's address space. You'll then setuid root to the compiled I/O enabler.
Here's some sample code adapted from the above source (be sure to use an address block you don't mind writing to):
#include <stdio.h>
#include <stdlib.h>
#include <sys/io.h>
#define DESIRED_PORT 0x300
#define NUM_BYTES 8
int main(int argc, char*argv[])
{
if (argc < 2) {
printf("Error: no target program specified.\n");
exit(1);
}
if (ioperm(DESIRED_PORT, NUM_BYTES, 1)) {
printf("Error: couldn't set port permissions.\n");
exit(1);
}
// Set uid to current user's id before executing the script
setgid(getgid());
setuid(getuid());
if (execvp(argv[1], &argv[1]) < 0) {
printf("Error: target program execution error.\n");
exit(1);
}
}
Let's call it io_enable.c, then compile and setuid root:
$ gcc io_enable.c -o io_enable
$ sudo chown root io_enable
$ sudo chmod u+s io_enable
Next, we can test it with the following python script:
#!/usr/bin/python
import portio
ADDR = 0x300
fd = open('/tmp/portio.log', 'w')
for i in range(10):
portio.outb(i, ADDR)
fd.write('Wrote %d, read %d.\n' % (i, portio.inb(ADDR)))
fd.close()
I'm calling it io_test.py and then running it like so:
$ ./io_enable python io_test.py
Looks like it works:
$ cat /tmp/portio.log
Wrote 0, read 0.
Wrote 1, read 1.
Wrote 2, read 2.
Wrote 3, read 3.
Wrote 4, read 4.
Wrote 5, read 5.
Wrote 6, read 6.
Wrote 7, read 7.
Wrote 8, read 8.
Wrote 9, read 9.
ps
can give you that information if you ask for the psr
column (or use the -F
flag which includes it).
Ex:
$ ps -F $$
UID PID PPID C SZ RSS PSR STIME TTY STAT TIME CMD
me 6415 6413 0 5210 2624 2 18:52 pts/0 SN 0:00 -su
Or:
$ ps -o pid,psr,comm -p $$
PID PSR COMMAND
6415 0 bash
My shell was running on CPU 2 when I ran the first command, on CPU 0 when I ran the second. Beware that processes can change CPUs very, very quickly so the information you actually see is, essentially, already stale.
Some more info in this Super User question's answers:
Linux: command to know the processor number in which a process is loaded?
Best Answer
As explained here, Linux puts a program's arguments in the program's data space, and keeps a pointer to the start of this area. This is what is used by
ps
and so on to find and show the program arguments.Since the data is in the program's space, it can manipulate it. Doing this without changing the program itself involves loading a shim with a
main()
function that will be called before the real main of the program. This shim can copy the real arguments to a new space, then overwrite the original arguments so thatps
will just see nuls.The following C code does this.
It is not possible to intervene on
main()
, but you can intervene on the standard C library function__libc_start_main
, which goes on to call main. Compile this fileshim_main.c
as noted in the comment at the start, and run it as shown. I've left aprintf
in the code so you check that it is actually being called. For example, runthen do a
ps
and you will see a blank command and args being shown.There is still a small amount of time that the command args may be visible. To avoid this, you could, for example, change the shim to read your secret from a file and add it to the args passed to the program.