Skip to content

Commit a426e8b

Browse files
committed
Overhaul IPC mechanism to use a Unix socket for increased security
The Atom-based IPC inherited from Ratpoison is overly complex and insecure. An unprivileged application that only has an X11 connection (say a sandboxed Firefox child process) could set the RP_COMMAND_REQUEST atom property on its window with a value of "0exec something" to cause us to read it and execute that shell command. Switching to a Unix socket in our ~/.config/sdorfehs directory ensures the requesting process is running as our uid, has the ability to make socket connections, and can write to that path.
1 parent 64ebd61 commit a426e8b

8 files changed

+163
-262
lines changed

communications.c

+132-100
Original file line numberDiff line numberDiff line change
@@ -23,125 +23,157 @@
2323
#include <X11/Xatom.h>
2424
#include <X11/Xproto.h>
2525

26+
#include <sys/socket.h>
27+
#include <sys/un.h>
28+
#include <fcntl.h>
2629
#include <string.h>
30+
#include <unistd.h>
31+
#include <err.h>
32+
#include <errno.h>
2733

2834
#include "sdorfehs.h"
2935

30-
/* Sending commands to us from another process */
31-
static int
32-
receive_command_result(Window w)
36+
void
37+
init_control_socket_path(void)
3338
{
34-
int query;
35-
int return_status = RET_FAILURE;
36-
Atom type_ret;
37-
int format_ret;
38-
unsigned long nitems;
39-
unsigned long bytes_after;
40-
unsigned char *result = NULL;
41-
42-
/* First, find out how big the property is. */
43-
query = XGetWindowProperty(dpy, w, rp_command_result,
44-
0, 0, False, xa_string,
45-
&type_ret, &format_ret, &nitems, &bytes_after,
46-
&result);
47-
48-
/* Failed to retrieve property. */
49-
if (query != Success || result == NULL) {
50-
PRINT_DEBUG(("failed to get command result length\n"));
51-
return return_status;
52-
}
53-
/*
54-
* XGetWindowProperty always allocates one extra byte even if the
55-
* property is zero length.
56-
*/
57-
XFree(result);
58-
59-
/*
60-
* Now that we have the length of the message, we can get the whole
61-
* message.
62-
*/
63-
query = XGetWindowProperty(dpy, w, rp_command_result,
64-
0, (bytes_after / 4) + (bytes_after % 4 ? 1 : 0),
65-
True, xa_string, &type_ret, &format_ret, &nitems,
66-
&bytes_after, &result);
67-
68-
/* Failed to retrieve property. */
69-
if (query != Success || result == NULL) {
70-
PRINT_DEBUG(("failed to get command result\n"));
71-
return return_status;
72-
}
73-
/*
74-
* We can receive:
75-
* - an empty string, indicating a success but no output
76-
* - a string starting with '1', indicating a success and an output
77-
* - a string starting with '0', indicating a failure and an optional
78-
* output
79-
*/
80-
switch (result[0]) {
81-
case '\0':
82-
/* Command succeeded but no string to print */
83-
return_status = RET_SUCCESS;
84-
break;
85-
case '0':
86-
/* Command failed, don't print an empty line if no explanation
87-
* was given */
88-
if (result[1] != '\0')
89-
fprintf(stderr, "%s\n", &result[1]);
90-
return_status = RET_FAILURE;
91-
break;
92-
case '1':
93-
/* Command succeeded, print the output */
94-
printf("%s\n", &result[1]);
95-
return_status = RET_SUCCESS;
96-
break;
97-
default:
98-
/* We probably got junk, so ignore it */
99-
return_status = RET_FAILURE;
100-
}
39+
char *config_dir;
10140

102-
/* Free the result. */
103-
XFree(result);
104-
105-
return return_status;
41+
config_dir = get_config_dir();
42+
rp_glob_screen.control_socket_path = xsprintf("%s/control", config_dir);
43+
free(config_dir);
10644
}
10745

108-
int
109-
send_command(unsigned char interactive, unsigned char *cmd)
46+
void
47+
listen_for_commands(void)
11048
{
111-
Window w, root;
112-
int done = 0, return_status = RET_FAILURE;
113-
struct sbuf *s;
49+
struct sockaddr_un sun;
50+
51+
if ((rp_glob_screen.control_socket_fd = socket(AF_UNIX,
52+
SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1)
53+
err(1, "socket");
11454

115-
s = sbuf_new(0);
116-
sbuf_printf(s, "%c%s", interactive, cmd);
55+
sun.sun_family = AF_UNIX;
56+
if (strlcpy(sun.sun_path, rp_glob_screen.control_socket_path,
57+
sizeof(sun.sun_path)) >= sizeof(sun.sun_path))
58+
err(1, "control socket path too long: %s",
59+
rp_glob_screen.control_socket_path);
60+
61+
if (unlink(rp_glob_screen.control_socket_path) == -1 &&
62+
errno != ENOENT)
63+
err(1, "unlink %s",rp_glob_screen.control_socket_path);
64+
65+
if (bind(rp_glob_screen.control_socket_fd, (struct sockaddr *)&sun,
66+
sizeof(sun)) == -1)
67+
err(1, "bind %s", rp_glob_screen.control_socket_path);
68+
69+
if (chmod(rp_glob_screen.control_socket_path, 0600) == -1)
70+
err(1, "chmod %s", rp_glob_screen.control_socket_path);
71+
72+
if (listen(rp_glob_screen.control_socket_fd, 2) == -1)
73+
err(1, "listen %s", rp_glob_screen.control_socket_path);
74+
75+
PRINT_DEBUG(("listening for commands at %s\n",
76+
rp_glob_screen.control_socket_path));
77+
}
11778

118-
root = RootWindow(dpy, DefaultScreen(dpy));
119-
w = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
79+
int
80+
send_command(int interactive, unsigned char *cmd)
81+
{
82+
struct sockaddr_un sun;
83+
char *wcmd;
84+
char ret[1024];
85+
size_t len;
86+
int fd;
87+
88+
len = 1 + strlen(cmd) + 2;
89+
wcmd = malloc(len);
90+
if (snprintf(wcmd, len, "%c%s\n", interactive, cmd) != (len - 1))
91+
errx(1, "snprintf");
92+
93+
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
94+
err(1, "socket");
95+
96+
sun.sun_family = AF_UNIX;
97+
if (strlcpy(sun.sun_path, rp_glob_screen.control_socket_path,
98+
sizeof(sun.sun_path)) >= sizeof(sun.sun_path))
99+
err(1, "control socket path too long: %s",
100+
rp_glob_screen.control_socket_path);
101+
102+
if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1)
103+
err(1, "failed to connect to control socket at %s",
104+
rp_glob_screen.control_socket_path);
105+
106+
if (write(fd, wcmd, len) != len)
107+
err(1, "short write to control socket");
108+
109+
free(wcmd);
110+
111+
len = read(fd, &ret, sizeof(ret) - 1);
112+
if (len > 2) {
113+
ret[len - 1] = '\0';
114+
fprintf(stderr, "%s\n", &ret[1]);
115+
}
120116

121-
/* Select first to avoid race condition */
122-
XSelectInput(dpy, w, PropertyChangeMask);
117+
return ret[0];
118+
}
123119

124-
XChangeProperty(dpy, w, rp_command, xa_string, 8, PropModeReplace,
125-
(unsigned char *)sbuf_get(s), strlen((char *) cmd) + 2);
120+
void
121+
receive_command(void)
122+
{
123+
cmdret *cmd_ret;
124+
char cmd[1024] = { 0 }, c;
125+
char *result, *rcmd;
126+
int cl, len = 0, interactive = 0;
126127

127-
XChangeProperty(dpy, root, rp_command_request, XA_WINDOW,
128-
8, PropModeAppend, (unsigned char *) &w, sizeof(Window));
128+
PRINT_DEBUG(("have connection waiting on command socket\n"));
129129

130-
sbuf_free(s);
130+
if ((cl = accept(rp_glob_screen.control_socket_fd, NULL, NULL)) == -1) {
131+
warn("accept");
132+
return;
133+
}
131134

132-
while (!done) {
133-
XEvent ev;
135+
while (len <= sizeof(cmd)) {
136+
if (len == sizeof(cmd)) {
137+
warn("%s: bogus command length", __func__);
138+
close(cl);
139+
return;
140+
}
134141

135-
XMaskEvent(dpy, PropertyChangeMask, &ev);
136-
if (ev.xproperty.atom == rp_command_result
137-
&& ev.xproperty.state == PropertyNewValue) {
138-
return_status =
139-
receive_command_result(ev.xproperty.window);
140-
done = 1;
142+
if (read(cl, &c, 1) == 1) {
143+
if (c == '\n') {
144+
cmd[len++] = '\0';
145+
break;
146+
}
147+
cmd[len++] = c;
148+
} else if (errno != EAGAIN) {
149+
PRINT_DEBUG(("bad read result on control socket: %s\n",
150+
strerror(errno)));
151+
break;
141152
}
142153
}
143154

144-
XDestroyWindow(dpy, w);
155+
interactive = cmd[0];
156+
rcmd = cmd + 1;
157+
158+
PRINT_DEBUG(("read %d byte(s) on command socket: %s\n", len, rcmd));
159+
160+
cmd_ret = command(interactive, rcmd);
161+
162+
/* notify the client of any text that was returned by the command */
163+
len = 2;
164+
if (cmd_ret->output) {
165+
result = xsprintf("%c%s\n", cmd_ret->success ? 1 : 0,
166+
cmd_ret->output);
167+
len = 1 + strlen(cmd_ret->output) + 1;
168+
} else if (cmd_ret->success)
169+
result = xsprintf("%c\n", 1);
170+
else
171+
result = xsprintf("%c\n", 0);
172+
173+
cmdret_free(cmd_ret);
174+
175+
PRINT_DEBUG(("writing back %d to command client: %s", len, result + 1));
145176

146-
return return_status;
177+
write(cl, result, len);
178+
close(cl);
147179
}

communications.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
#ifndef _SDORFEHS_COMMUNICATIONS_H
2121
#define _SDORFEHS_COMMUNICATIONS_H 1
2222

23-
int send_command(unsigned char interactive, unsigned char *cmd);
23+
void init_control_socket_path(void);
24+
void listen_for_commands(void);
25+
int send_command(int interactive, unsigned char *cmd);
26+
void receive_command(void);
2427

2528
#endif /* ! _SDORFEHS_COMMUNICATIONS_H */

data.h

+4
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ struct rp_global_screen {
168168
/* This numset is responsible for giving out numbers for each screen */
169169
struct numset *numset;
170170

171+
/* The path to and open fd of our control socket */
172+
char *control_socket_path;
173+
int control_socket_fd;
174+
171175
/* The path to and open fd of our bar FIFO */
172176
char *bar_fifo_path;
173177
int bar_fifo_fd;

0 commit comments

Comments
 (0)