runas

runas is a tool that executes commands as other users. It sets up xauth and some environmental variables so that X applications (currently I am running gaim and firefox through runas) work.

Why?

The rationale is similar to Vista's reduced privilege mode for IE7. If a remote vulnerability is exploited in an application the damage is limited (unless other local exploits are used to gain root privileges).

Although their goals are similar, runas was not inspired by the so-called "Low-Rights IE". I first had the idea and initial implementation (which never worked) some time in 2005, before such a mode was announced for IE7. Obviously numerous daemons before that also implemented their own privilege dropping. Unlike those mechanisms, runas enables privilege dropping for arbitrary programs that did not have the feature built in.

Usage

Extract the tarball, make && make install, then copy runas.conf to /etc and modify for your environment. runas does not create the users and groups listed in runas.conf, so you will need to create them yourself:

sudo adduser --disabled-login webbrowser

If you want to keep your existing profiles for firefox where they are, some extra steps are needed:

sudo chgrp -R webbrowser .mozilla
# make files g+rwx where appropriate 
for f in `find .mozilla`; do if [ -x $f ]; then sudo chmod g+x $f; fi; done
for f in `find .mozilla`; do if [ -w $f ]; then sudo chmod g+w $f; fi; done
for f in `find .mozilla`; do if [ -r $f ]; then sudo chmod g+r $f; fi; done

You also need to change your default browser to runas /usr/bin/firefox %s if you want links from other applications to work correctly. If you use GNOME, this can be done in gnome-default-applications-properties.

In addition to that, I also created a ~/Desktop/Downloads directory under my normal user and marked it group webbrowser and group writable. I changed my firefox download directory to that so I can grab the files I downloaded easily.

I've only tested runas under Linux (Ubuntu Edgy), although it will probably work in other unix environments assuming that glib and the development headers are installed.

How secure is this?

Since runas is a setuid binary, Very Bad Things can happen if something goes wrong. Just keep in mind that runas comes with no warranty :-)

runas also does not prevent exploits like the recent Nvidia driver exploit that directly target the X server/drivers.

runas.conf should only list commands using absolute path.

runas is also not suited for environments where there are more than one real user, because the configuration is global. This may change in a later version.

by khc on Sun Oct 22 15:21:37 2006 Permlink
Tags: computer

libdebug

debug.c:

#include <glib.h>

#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>

void ld_attach() __attribute((constructor));
static gboolean ld_read_cb(GIOChannel *source, GIOCondition cond,
                           gpointer data);
static gboolean ld_conn_cb(GIOChannel *source, GIOCondition cond,
                           gpointer data);
static int ld_create_socket();

static guint source_id = -1;

void
ld_attach()
{
    int fd = ld_create_socket();

    if (fd == -1) {
        return;
    }

    GIOChannel *channel = g_io_channel_unix_new(fd);

    source_id = g_io_add_watch(channel, G_IO_IN | G_IO_HUP, ld_conn_cb, NULL);
}

static gboolean
ld_conn_cb(GIOChannel *channel, GIOCondition cond, gpointer data)
{
    struct sockaddr_in in_addr;
    socklen_t size = -1;

    if (cond == G_IO_IN) {
        int fd = g_io_channel_unix_get_fd(channel);
        int new_fd = -1;

        size = sizeof(in_addr);
        if ((new_fd = accept(fd, (struct sockaddr *)&in_addr, &size)) == -1) {
            perror("accept");
            return TRUE;
        }

        GIOChannel *channel2 = g_io_channel_unix_new(new_fd);
        source_id = g_io_add_watch(channel2, G_IO_IN | G_IO_HUP, ld_read_cb,
                                   NULL);
    }

    return TRUE;
}

static gboolean
ld_read_cb(GIOChannel *channel, GIOCondition cond, gpointer data)
{
    int addr = 0;
    char *line = NULL;

    if (cond == G_IO_IN) {
        while (g_io_channel_read_line(channel, &line, NULL, NULL, NULL) == 
               G_IO_STATUS_NORMAL) {
            if (sscanf(line, "%x\n", &addr) == 1) {
                char *ptr = GINT_TO_POINTER(addr);
                int i = 0;

                printf("Reading address %p: ", ptr);

                for (i = 0; i < 10 && *ptr; i++, ptr++) {
                    printf("%c", *ptr);
                }

                printf("\n");
            }
        }
    } else if (cond == G_IO_ERR || cond == G_IO_HUP) {
        g_io_channel_shutdown(channel, FALSE, NULL);
    }

    return TRUE;
}

static int
ld_create_socket()
{
    int fd = -1;
    struct sockaddr_in addr;

    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        return -1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(34567);

    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("bind");
        return -1;
    }

    if (listen(fd, 0) == -1) {
        perror("listen");
        return -1;
    }

    return fd;
}

main.c:

#include <stdio.h>
#include <glib.h>

int
main(int argc, char **argv)
{
    GMainLoop *mainloop = g_main_loop_new(NULL, FALSE);
    printf("%p: %s\n", argv[0], argv[0]);

    g_main_loop_run(mainloop);

    return 0;
}

Makefile:

LSRC=                                           \
    debug.c                                     \


SRC=                                            \
    main.c                                      \


LOBJ = $(LSRC:.c=.o)

OBJ = $(SRC:.c=.o)

CFLAGS += -Wall -g -Werror -Wextra -Wfloat-equal -Wbad-function-cast -Wcast-qual -Winline -Wno-unused-parameter
CFLAGS += `pkg-config --cflags glib-2.0` -I$(PWD)
LFLAGS += `pkg-config --libs glib-2.0`

LIBOUT = libdebug.so

OUT = test

all: $(LIBOUT) $(OUT)

$(OUT): $(OBJ)
    $(CC) $(CFLAGS) $(LFLAGS) $(OBJ) -L$(PWD) -o $(OUT)

$(LIBOUT): $(LOBJ)
    $(CC) -shared -Wl,-soname,$(LIBOUT) $(CFLAGS) $(LFLAGS) $(LOBJ) -o $(LIBOUT)

$(OBJ): %.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

$(LOBJ): %.o: %.c
    $(CC) -fPIC $(CFLAGS) -c $< -o $@

clean:
    $(RM) $(LOBJ) $(LIBOUT) $(OUT) $(OBJ)

To run:

$ make
$ LD_PRELOAD=./libdebug.so ./test
0xbfc5b9b7: ./test

Then in another terminal:

$ telnet 127.0.0.1 34567
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
0xbfc5b9b7

You should see this printed out in the original terminal:

$ LD_PRELOAD=./libdebug.so ./test
0xbfc5b9b7: ./test
Reading address 0xbfc5b9b7: ./test

Printing the result in the telnet window is left as an exercise for the reader.

by khc on Sat Sep 30 21:44:59 2006 Permlink
Tags: computer

RSS feed

Andrew, click here.

by khc on Mon Jul 17 23:33:13 2006 Permlink
Tags: computer

Google Analytics

Finally received my Google Analytics Invitation Code. Will be checking in the next few days to see what kind of statistics it gives, and how it compares with Awstats.

First impression is that the UI is somewhat laggy. The main report pages are driven by flash, and loading the flash content isn't exactly snappy. The latency with Web applications still leave a lot to be desired.

Google Analytics is also targeting business users rather than hobbyists. It has lots of buzzwords like "Marketing Optimization" and "Goal Conversion" that I have no idea about. I don't think I will use this for over a month.

by khc on Mon Jun 5 20:29:12 2006 Permlink
Tags: computer

Walking the Stack/Heap/Data Section

Been experimenting with writing a garbage collector lately. One of the problems I faced was how to walk the stack/heap/data section for all the (potential) pointers. The solution that I come up with probably only works for 32bit x86 Linux, and even then maybe making some bad assumption about memory alignment, among other things. Still, this maybe useful for other curious minds out there.

First there are etext, edata and end. Those 3 symbols are not normally defined, so you can define variables with those names if you want. But if you do:

extern unsigned int etext, edata, end;

ld (or maybe something else, I can't find where I originally read this) will define those symbols if they don't resolve to anything else. Their values are meaningless, only their addresses are useful. &etext is the starting address for the text section, &edata is the end of text section and also the beginning of data section. &end is the end of data section and also the beginning of the heap. The type that you declare etext et al with is probably unimportant.

For memory allocators that use sbrk(), you can also use sbrk(0) to find the end of the heap. I've read that glibc uses mmap for malloc()s that are over certain sizes, so that may break things if you mix sbrk(0) and malloc().

The stack is a little bit trickier, everywhere I've looked suggests that there is no portable way to walk the stack. Here's the code snippet that I am using:

register gulong *fp __asm ("%ebp");

/* skip the current frame because I don't want to walk the current
   function */
gulong *ptr = (gulong *)*fp;
do {
    gulong *saved = ptr + 4;
    ptr = (gulong *)*ptr;

    if (ptr == NULL) {
        break;
    }

    // do something from "saved" to "ptr"
} while (1);

As you can see, I first define a variable for %ebp, which is the x86 way of saying $fp. 0(%ebp) contains the address of the previous frame pointer, the last of which points to NULL. Incrementing by 4 maybe a bad assumption though, there maybe cases where 2 is more appropriate.

by khc on Mon May 22 20:23:48 2006 Permlink
Tags: computer
Older Posts