* cons.saver.c: Partly rewritten cons.saver.c.

This commit is contained in:
Andrew V. Samoilov 2004-10-05 07:14:09 +00:00
parent 2d48b95137
commit 919aa12d7d
2 changed files with 173 additions and 264 deletions

View File

@ -1,3 +1,7 @@
2004-09-10 Jakub Jelinek <jakub@redhat.com>
* cons.saver.c: Partly rewritten cons.saver.c.
2004-09-28 Andrew V. Samoilov <sav@bcs.zp.ua> 2004-09-28 Andrew V. Samoilov <sav@bcs.zp.ua>
* util.c (convert_controls): Fix possible buffer overflow * util.c (convert_controls): Fix possible buffer overflow
@ -224,7 +228,7 @@
From Petr Hadraba <hadrabap@volny.cz> From Petr Hadraba <hadrabap@volny.cz>
2004-09-10 Jakub Jelinek <jakub@redhat.com>
* view.c (get_byte): Fix avoid dying if file is too large * view.c (get_byte): Fix avoid dying if file is too large
to fit into memory. to fit into memory.

View File

@ -1,9 +1,13 @@
#ifdef __linux__ /* This program should be setuid vcsa and /dev/vcsa* should be
owned by the same user too.
Partly rewritten by Jakub Jelinek <jakub@redhat.com>. */
/* General purpose Linux console screen save/restore server /* General purpose Linux console screen save/restore server
Copyright (C) 1994 Janne Kukonlehto <jtklehto@stekt.oulu.fi> Copyright (C) 1994 Janne Kukonlehto <jtklehto@stekt.oulu.fi>
Original idea from Unix Interactive Tools version 3.2b (tty.c) Original idea from Unix Interactive Tools version 3.2b (tty.c)
This code requires root privileges. This code requires root privileges.
You may want to make the cons.saver setuid root.
The code should be safe even if it is setuid but who knows?
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,300 +23,201 @@
along with this program; if not, write to the Free Software along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/* cons.saver is run by MC to save and restore screen contents on Linux /* This code does _not_ need to be setuid root. However, it needs
virtual console. This is done by using file /dev/vcsaN or /dev/vcc/aN, read/write access to /dev/vcsa* (which is priviledged
where N is the number of the virtual console. operation). You should create user vcsa, make cons.saver setuid
In a properly set up system, /dev/vcsaN should become accessible user vcsa, and make all vcsa's owned by user vcsa.
by the user when the user logs in on the corresponding console
/dev/ttyN or /dev/vc/N. In this case, cons.saver doesn't need to be
suid root. However, if /dev/vcsaN is not accessible by the user,
cons.saver can be made setuid root - this program is designed to be
safe even in this case. */
#include <config.h> Seeing other peoples consoles is bad thing, but believe me, full
#include <sys/types.h> root is even worse. */
#include <sys/stat.h>
#include <sys/ioctl.h> #ifndef _GNU_SOURCE
#include <fcntl.h> #define _GNU_SOURCE
#include <errno.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif #endif
#include <fcntl.h>
#include <unistd.h>
#include <termios.h> #include <termios.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <ctype.h> /* For isdigit() */
#include <string.h> #include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#define LINUX_CONS_SAVER_C #define LINUX_CONS_SAVER_C
#include "cons.saver.h" #include "cons.saver.h"
#define cmd_input 0 void
#define cmd_output 1 send_contents (char *buffer, unsigned int columns, unsigned int rows)
static int console_minor = 0;
static char *buffer = NULL;
static int buffer_size = 0;
static int columns, rows;
static int vcs_fd;
/*
* Get window size for the given terminal.
* Return 0 for success, -1 otherwise.
*/
static int tty_getsize (int console_fd)
{ {
struct winsize winsz; unsigned char begin_line = 0, end_line = 0;
unsigned int lastline, index, x;
unsigned char message, outbuf[1024], *p;
unsigned short bytes;
winsz.ws_col = winsz.ws_row = 0; index = 2 * rows * columns;
ioctl (console_fd, TIOCGWINSZ, &winsz); for (lastline = rows; lastline > 0; lastline--)
if (winsz.ws_col && winsz.ws_row) { for (x = 0; x < columns; x++)
columns = winsz.ws_col; {
rows = winsz.ws_row; index -= 2;
return 0; if (buffer [index] != ' ')
} goto out;
}
out:
return -1; message = CONSOLE_CONTENTS;
} write (1, &message, 1);
/* read (0, &begin_line, 1);
* Do various checks to make sure that the supplied filename is read (0, &end_line, 1);
* a suitable tty device. If check_console is set, check that we if (begin_line > lastline)
* are dealing with a Linux virtual console. begin_line = lastline;
* Return 0 for success, -1 otherwise. if (end_line > lastline)
*/ end_line = lastline;
static int check_file (char *filename, int check_console)
{
int fd;
struct stat stat_buf;
/* Avoiding race conditions: use of fstat makes sure that index = (end_line - begin_line) * columns;
both 'open' and 'stat' operate on the same file */ bytes = index;
if (index != bytes)
bytes = 0;
write (1, &bytes, 2);
if (! bytes)
return;
fd = open (filename, O_RDWR); p = outbuf;
if (fd == -1) for (index = 2 * begin_line * columns;
return -1; index < 2 * end_line * columns;
index += 2)
do {
if (fstat (fd, &stat_buf) == -1)
break;
/* Must be character device */
if (!S_ISCHR (stat_buf.st_mode)){
break;
}
if (check_console){
/* Major number must be 4 */
if ((stat_buf.st_rdev & 0xff00) != 0x0400){
break;
}
/* Minor number must be between 1 and 63 */
console_minor = (int) (stat_buf.st_rdev & 0x00ff);
if (console_minor < 1 || console_minor > 63){
break;
}
/* Must be owned by the user */
if (stat_buf.st_uid != getuid ()){
break;
}
}
/* Everything seems to be okay */
return fd;
} while (0);
close (fd);
return -1;
}
/*
* Check if the supplied filename is a Linux virtual console.
* Return 0 if successful, -1 otherwise.
* Since the tty name is supplied by the user and cons.saver can be
* a setuid program, many checks have to be done to prevent possible
* security compromise.
*/
static int detect_console (char *tty_name)
{
char console_name [16];
static char vcs_name [16];
int console_fd;
/* Must be console */
console_fd = check_file (tty_name, 1);
if (console_fd == -1)
return -1;
if (tty_getsize (console_fd) == -1) {
close (console_fd);
return -1;
}
close (console_fd);
/*
* Only allow /dev/ttyMINOR and /dev/vc/MINOR where MINOR is the minor
* device number of the console, set in check_file()
*/
switch (tty_name[5])
{ {
case 'v': *p++ = buffer [index];
snprintf (console_name, sizeof (console_name), "/dev/vc/%d", if (p == outbuf + sizeof (outbuf))
console_minor); {
if (strncmp (console_name, tty_name, sizeof (console_name)) != 0) write (1, outbuf, sizeof (outbuf));
return -1; p = outbuf;
break; }
case 't':
snprintf (console_name, sizeof (console_name), "/dev/tty%d",
console_minor);
if (strncmp (console_name, tty_name, sizeof (console_name)) != 0)
return -1;
break;
default:
return -1;
} }
snprintf (vcs_name, sizeof (vcs_name), "/dev/vcsa%d", console_minor); if (p != outbuf)
vcs_fd = check_file (vcs_name, 0); write (1, outbuf, p - outbuf);
}
/* Try devfs name */ void __attribute__ ((noreturn))
if (vcs_fd == -1) { die (void)
snprintf (vcs_name, sizeof (vcs_name), "/dev/vcc/a%d", console_minor); {
vcs_fd = check_file (vcs_name, 0); unsigned char zero = 0;
write (1, &zero, 1);
exit (3);
}
int
main (int argc, char **argv)
{
unsigned char action = 0, console_flag = 3;
int console_fd, vcsa_fd, console_minor, buffer_size;
struct stat st;
uid_t uid, euid;
char *buffer, *tty_name, console_name [16], vcsa_name [16], *p, *q;
struct winsize winsz;
close (2);
if (argc != 2)
die ();
tty_name = argv [1];
if (strnlen (tty_name, 15) == 15
|| strncmp (tty_name, "/dev/", 5))
die ();
setsid ();
uid = getuid ();
euid = geteuid ();
if (seteuid (uid) < 0)
die ();
console_fd = open (tty_name, O_RDONLY);
if (console_fd < 0)
die ();
if (fstat (console_fd, &st) < 0 || ! S_ISCHR (st.st_mode))
die ();
if ((st.st_rdev & 0xff00) != 0x0400)
die ();
console_minor = (int) (st.st_rdev & 0x00ff);
if (console_minor < 1 || console_minor > 63)
die ();
if (st.st_uid != uid)
die ();
switch (tty_name [5])
{
/* devfs */
case 'v': p = "/dev/vc/%d"; q = "/dev/vcc/a%d"; break;
/* /dev/ttyN */
case 't': p = "/dev/tty%d"; q = "/dev/vcsa%d"; break;
default: die (); break;
} }
if (vcs_fd == -1) snprintf (console_name, sizeof (console_name), p, console_minor);
return -1; if (strncmp (console_name, tty_name, sizeof (console_name)) != 0)
die ();
return 0; if (seteuid (euid) < 0)
} die ();
static void save_console (void) snprintf (vcsa_name, sizeof (vcsa_name), q, console_minor);
{ vcsa_fd = open (vcsa_name, O_RDWR);
lseek (vcs_fd, 0, SEEK_SET); if (vcsa_fd < 0)
read (vcs_fd, buffer, buffer_size); die ();
} if (fstat (vcsa_fd, &st) < 0 || ! S_ISCHR (st.st_mode))
die ();
static void restore_console (void) if (seteuid (uid) < 0)
{ die ();
lseek (vcs_fd, 0, SEEK_SET);
write (vcs_fd, buffer, buffer_size);
}
static void send_contents (void) winsz.ws_col = winsz.ws_row = 0;
{ if (ioctl (console_fd, TIOCGWINSZ, &winsz) < 0
unsigned char begin_line=0, end_line=0; || winsz.ws_col <= 0 || winsz.ws_row <= 0
int index; || winsz.ws_col >= 256 || winsz.ws_row >= 256)
unsigned char message; die ();
unsigned short bytes;
const int bytes_per_char = 2;
/* Inform the invoker that we can handle this command */ buffer_size = 4 + 2 * winsz.ws_col * winsz.ws_row;
message = CONSOLE_CONTENTS; buffer = calloc (buffer_size, 1);
write (cmd_output, &message, 1); if (buffer == NULL)
die ();
/* Read the range of lines wanted */ write (1, &console_flag, 1);
read (cmd_input, &begin_line, 1);
read (cmd_input, &end_line, 1);
if (begin_line > rows)
begin_line = rows;
if (end_line > rows)
end_line = rows;
/* Tell the invoker how many bytes it will be */ while (console_flag && read (0, &action, 1) == 1)
bytes = (end_line - begin_line) * columns; {
write (cmd_output, &bytes, 2); switch (action)
{
/* Send the contents */
for (index = (2 + begin_line * columns) * bytes_per_char;
index < (2 + end_line * columns) * bytes_per_char;
index += bytes_per_char)
write (cmd_output, buffer + index, 1);
/* All done */
}
int main (int argc, char **argv)
{
unsigned char action = 0;
int stderr_fd;
/* 0 - not a console, 3 - supported Linux console */
signed char console_flag = 0;
/*
* Make sure stderr points to a valid place
*/
close (2);
stderr_fd = open ("/dev/tty", O_RDWR);
/* This may well happen if program is running non-root */
if (stderr_fd == -1)
stderr_fd = open ("/dev/null", O_RDWR);
if (stderr_fd == -1)
exit (1);
if (stderr_fd != 2)
while (dup2 (stderr_fd, 2) == -1 && errno == EINTR)
;
if (argc != 2){
/* Wrong number of arguments */
write (cmd_output, &console_flag, 1);
return 3;
}
/* Lose the control terminal */
setsid ();
/* Check that the argument is a Linux console */
if (detect_console (argv [1]) == -1) {
/* Not a console -> no need for privileges */
setuid (getuid ());
} else {
console_flag = 3;
/* Allocate buffer for screen image */
buffer_size = 4 + 2 * columns * rows;
buffer = (char*) malloc (buffer_size);
}
/* Inform the invoker about the result of the tests */
write (cmd_output, &console_flag, 1);
/* Read commands from the invoker */
while (console_flag && read (cmd_input, &action, 1)){
/* Handle command */
switch (action){
case CONSOLE_DONE: case CONSOLE_DONE:
console_flag = 0; console_flag = 0;
continue; /* Break while loop instead of switch clause */ continue;
case CONSOLE_SAVE: case CONSOLE_SAVE:
save_console (); if (seteuid (euid) < 0
break; || lseek (vcsa_fd, 0, 0) != 0
|| fstat (console_fd, &st) < 0 || st.st_uid != uid
|| read (vcsa_fd, buffer, buffer_size) != buffer_size
|| fstat (console_fd, &st) < 0 || st.st_uid != uid)
memset (buffer, 0, buffer_size);
if (seteuid (uid) < 0)
die ();
break;
case CONSOLE_RESTORE: case CONSOLE_RESTORE:
restore_console (); if (seteuid (euid) >= 0
break; && lseek (vcsa_fd, 0, 0) == 0
&& fstat (console_fd, &st) >= 0 && st.st_uid == uid)
write (vcsa_fd, buffer, buffer_size);
if (seteuid (uid) < 0)
die ();
break;
case CONSOLE_CONTENTS: case CONSOLE_CONTENTS:
send_contents (); send_contents (buffer + 4, winsz.ws_col, winsz.ws_row);
break; break;
} /* switch (action) */ }
/* Inform the invoker that command has been handled */
write (cmd_output, &console_flag, 1);
} /* while (read ...) */
if (buffer) write (1, &console_flag, 1);
free (buffer); }
return 0;
exit (0);
} }
#else
#error "The Linux console screen saver works only on Linux"
#endif /* __linux__ */