client: Fix rogue execute protection

To protect against a rogue server reading your files, the client
honors C_EXECUTE only when it matches recent player input.

This has a somewhat troubled history, detailed in the previous commit.

The remaining major issue comes from commit 8b7d0b9 (v4.3.11): any
suffix of a recent line of input is accepted as C_EXECUTE text.
Before, only text that looked like an argument of an execute command
or a redirection was accepted.

Fix by again requiring the text to be preceded by something that looks
like an execute command.  But do it more carefully: don't break
execute with a prompted for argument, and prevent abuse of
redirections for execute.

Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
This commit is contained in:
Markus Armbruster 2015-12-27 20:22:45 +01:00
parent d13950470a
commit 5cb14f508e
5 changed files with 83 additions and 24 deletions

View file

@ -157,16 +157,17 @@ ring_discard(struct ring *r, int n)
/*
* Search the ring buffer for zero-terminated string S.
* If found, return a non-negative value referring to the beginning of
* S in the buffer when passed to ring_peek(). Else return -1.
* Start at the @(n+1)-th byte to be gotten.
* If found, return the number of bytes in the buffer before S.
* Else return -1.
*/
int
ring_search(struct ring *r, char *s)
ring_search(struct ring *r, char *s, int n)
{
size_t len = strlen(s);
size_t i, j;
for (i = r->cons; i + len <= r->prod; i++) {
for (i = r->cons + n; i + len <= r->prod; i++) {
for (j = 0; s[j] && s[j] == (char)r->buf[(i + j) % RING_SIZE]; j++)
;
if (!s[j])

View file

@ -27,7 +27,7 @@
* ringbuf.h: Simple ring buffer
*
* Known contributors to this file:
* Markus Armbruster, 2007
* Markus Armbruster, 2007-2015
*/
#ifndef RINGBUF_H
@ -59,7 +59,7 @@ extern int ring_getc(struct ring *);
extern int ring_putc(struct ring *, unsigned char);
extern int ring_putm(struct ring *, void *, size_t);
extern void ring_discard(struct ring *, int);
extern int ring_search(struct ring *, char *);
extern int ring_search(struct ring *, char *, int);
extern int ring_from_file(struct ring *, int fd);
extern int ring_to_file(struct ring *, int fd);

View file

@ -33,6 +33,8 @@
#include <config.h>
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "ringbuf.h"
#include "secure.h"
@ -52,7 +54,7 @@ save_input(char *inp)
assert(len && inp[len - 1] == '\n');
while (ring_putm(&recent_input, inp, len) < 0) {
eol = ring_search(&recent_input, "\n");
eol = ring_search(&recent_input, "\n", 0);
assert(eol >= 0);
ring_discard(&recent_input, eol + 1);
}
@ -68,5 +70,42 @@ seen_input(char *tail)
size_t len = strlen(tail);
assert(len && tail[len - 1] == '\n');
return ring_search(&recent_input, tail) >= 0;
return ring_search(&recent_input, tail, 0) >= 0;
}
/*
* Can you still remember input that looks like an execute @arg?
* @arg must end with a newline.
*/
int
seen_exec_input(char *arg)
{
size_t len = strlen(arg);
int n, i, j, ch;
unsigned char buf[RING_SIZE + 1];
assert(len && arg[len - 1] == '\n');
n = 1;
for (;;) {
/* find next line ending with arg */
n = ring_search(&recent_input, arg, n + 1);
if (n <= 0)
return 0;
/* extract command (same or previous line) */
i = n - 1;
if (ring_peek(&recent_input, i) == '\n')
i--;
j = sizeof(buf);
buf[--j] = 0;
for (; i >= 0 && (ch = ring_peek(&recent_input, i)) != '\n'; i--)
buf[--j] = ch;
/* compare command */
for (; isspace(buf[j]); j++) ;
for (i = j; buf[i] && !isspace(buf[i]); i++) ;
if (i - j >= 2 && !strncmp("execute", (char *)buf + j, i - j))
return 1;
}
}

View file

@ -37,5 +37,6 @@
extern void save_input(char *);
extern int seen_input(char *);
extern int seen_exec_input(char *);
#endif

View file

@ -154,20 +154,8 @@ fname(char *s)
}
static int
redir_authorized(char *arg, char *attempt, int expected)
common_authorized(char *arg, char *attempt)
{
if (!expected) {
fprintf(stderr, "Warning: dropped conflicting %s %s",
attempt, arg);
return 0;
}
if (!seen_input(arg)) {
fprintf(stderr, "Warning: server attempted to %s %s",
attempt, arg);
return 0;
}
if (restricted) {
fprintf(stderr, "Can't %s in restricted mode\n", attempt);
return 0;
@ -180,13 +168,43 @@ redir_authorized(char *arg, char *attempt, int expected)
return 1;
}
static int
redir_authorized(char *arg, char *attempt)
{
if (redir_fp) {
fprintf(stderr, "Warning: dropped conflicting %s %s",
attempt, arg);
return 0;
}
if (!seen_input(arg)) {
fprintf(stderr, "Warning: server attempted to %s %s",
attempt, arg);
return 0;
}
return common_authorized(arg, attempt);
}
static int
exec_authorized(char *arg)
{
if (!seen_exec_input(arg)) {
fprintf(stderr,
"Warning: server attempted to execute batch file %s", arg);
return 0;
}
return common_authorized(arg, "execute batch file");
}
static void
doredir(char *p)
{
int mode;
int fd;
if (!redir_authorized(p, "redirect to file", !redir_fp))
if (!redir_authorized(p, "redirect to file"))
return;
if (*p++ != '>') {
fprintf(stderr, "Warning: dropped weird redirection %s", p);
@ -221,7 +239,7 @@ doredir(char *p)
static void
dopipe(char *p)
{
if (!redir_authorized(p, "pipe to shell command", !redir_fp))
if (!redir_authorized(p, "pipe to shell command"))
return;
if (*p++ != '|') {
fprintf(stderr, "Warning: dropped weird pipe %s", p);
@ -250,7 +268,7 @@ doexecute(char *p)
{
int fd;
if (!redir_authorized(p, "execute batch file", 1))
if (!exec_authorized(p))
return -1;
p = fname(p);