1 /*
2  * This file is part of mpv.
3  *
4  * mpv is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * mpv is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <windows.h>
19 #include <sddl.h>
20 
21 #include "config.h"
22 
23 #include "osdep/io.h"
24 #include "osdep/threads.h"
25 #include "osdep/windows_utils.h"
26 
27 #include "common/common.h"
28 #include "common/global.h"
29 #include "common/msg.h"
30 #include "input/input.h"
31 #include "libmpv/client.h"
32 #include "options/m_config.h"
33 #include "options/options.h"
34 #include "player/client.h"
35 
36 struct mp_ipc_ctx {
37     struct mp_log *log;
38     struct mp_client_api *client_api;
39     const wchar_t *path;
40 
41     pthread_t thread;
42     HANDLE death_event;
43 };
44 
45 struct client_arg {
46     struct mp_log *log;
47     struct mpv_handle *client;
48 
49     char *client_name;
50     HANDLE client_h;
51     bool writable;
52     OVERLAPPED write_ol;
53 };
54 
55 // Get a string SID representing the current user. Must be freed by LocalFree.
get_user_sid(void)56 static char *get_user_sid(void)
57 {
58     char *ssid = NULL;
59     TOKEN_USER *info = NULL;
60     HANDLE t;
61     if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &t))
62         goto done;
63 
64     DWORD info_len;
65     if (!GetTokenInformation(t, TokenUser, NULL, 0, &info_len) &&
66         GetLastError() != ERROR_INSUFFICIENT_BUFFER)
67         goto done;
68 
69     info = talloc_size(NULL, info_len);
70     if (!GetTokenInformation(t, TokenUser, info, info_len, &info_len))
71         goto done;
72     if (!info->User.Sid)
73         goto done;
74 
75     ConvertSidToStringSidA(info->User.Sid, &ssid);
76 done:
77     if (t)
78         CloseHandle(t);
79     talloc_free(info);
80     return ssid;
81 }
82 
83 // Get a string SID for the process integrity level. Must be freed by LocalFree.
get_integrity_sid(void)84 static char *get_integrity_sid(void)
85 {
86     char *ssid = NULL;
87     TOKEN_MANDATORY_LABEL *info = NULL;
88     HANDLE t;
89     if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &t))
90         goto done;
91 
92     DWORD info_len;
93     if (!GetTokenInformation(t, TokenIntegrityLevel, NULL, 0, &info_len) &&
94         GetLastError() != ERROR_INSUFFICIENT_BUFFER)
95         goto done;
96 
97     info = talloc_size(NULL, info_len);
98     if (!GetTokenInformation(t, TokenIntegrityLevel, info, info_len, &info_len))
99         goto done;
100     if (!info->Label.Sid)
101         goto done;
102 
103     ConvertSidToStringSidA(info->Label.Sid, &ssid);
104 done:
105     if (t)
106         CloseHandle(t);
107     talloc_free(info);
108     return ssid;
109 }
110 
111 // Create a security descriptor that only grants access to processes running
112 // under the current user at the current integrity level or higher
create_restricted_sd(void)113 static PSECURITY_DESCRIPTOR create_restricted_sd(void)
114 {
115     char *user_sid = get_user_sid();
116     char *integrity_sid = get_integrity_sid();
117     if (!user_sid || !integrity_sid)
118         return NULL;
119 
120     char *sddl = talloc_asprintf(NULL,
121         "O:%s"                 // Set the owner to user_sid
122         "D:(A;;GRGW;;;%s)"     // Grant GENERIC_{READ,WRITE} access to user_sid
123         "S:(ML;;NRNWNX;;;%s)", // Disallow read, write and execute permissions
124                                // to integrity levels below integrity_sid
125         user_sid, user_sid, integrity_sid);
126     LocalFree(user_sid);
127     LocalFree(integrity_sid);
128 
129     PSECURITY_DESCRIPTOR sd = NULL;
130     ConvertStringSecurityDescriptorToSecurityDescriptorA(sddl, SDDL_REVISION_1,
131         &sd, NULL);
132     talloc_free(sddl);
133 
134     return sd;
135 }
136 
wakeup_cb(void * d)137 static void wakeup_cb(void *d)
138 {
139     HANDLE event = d;
140     SetEvent(event);
141 }
142 
143 // Wrapper for ReadFile that treats ERROR_IO_PENDING as success
async_read(HANDLE file,void * buf,unsigned size,OVERLAPPED * ol)144 static DWORD async_read(HANDLE file, void *buf, unsigned size, OVERLAPPED* ol)
145 {
146     DWORD err = ReadFile(file, buf, size, NULL, ol) ? 0 : GetLastError();
147     return err == ERROR_IO_PENDING ? 0 : err;
148 }
149 
150 // Wrapper for WriteFile that treats ERROR_IO_PENDING as success
async_write(HANDLE file,const void * buf,unsigned size,OVERLAPPED * ol)151 static DWORD async_write(HANDLE file, const void *buf, unsigned size, OVERLAPPED* ol)
152 {
153     DWORD err = WriteFile(file, buf, size, NULL, ol) ? 0 : GetLastError();
154     return err == ERROR_IO_PENDING ? 0 : err;
155 }
156 
pipe_error_is_fatal(DWORD error)157 static bool pipe_error_is_fatal(DWORD error)
158 {
159     switch (error) {
160     case 0:
161     case ERROR_HANDLE_EOF:
162     case ERROR_BROKEN_PIPE:
163     case ERROR_PIPE_NOT_CONNECTED:
164     case ERROR_NO_DATA:
165         return false;
166     }
167     return true;
168 }
169 
ipc_write_str(struct client_arg * arg,const char * buf)170 static DWORD ipc_write_str(struct client_arg *arg, const char *buf)
171 {
172     DWORD error = 0;
173 
174     if ((error = async_write(arg->client_h, buf, strlen(buf), &arg->write_ol)))
175         goto done;
176     if (!GetOverlappedResult(arg->client_h, &arg->write_ol, &(DWORD){0}, TRUE)) {
177         error = GetLastError();
178         goto done;
179     }
180 
181 done:
182     if (pipe_error_is_fatal(error)) {
183         MP_VERBOSE(arg, "Error writing to pipe: %s\n",
184             mp_HRESULT_to_str(HRESULT_FROM_WIN32(error)));
185     }
186 
187     if (error)
188         arg->writable = false;
189     return error;
190 }
191 
report_read_error(struct client_arg * arg,DWORD error)192 static void report_read_error(struct client_arg *arg, DWORD error)
193 {
194     // Only report the error if it's not just due to the pipe closing
195     if (pipe_error_is_fatal(error)) {
196         MP_ERR(arg, "Error reading from pipe: %s\n",
197             mp_HRESULT_to_str(HRESULT_FROM_WIN32(error)));
198     } else {
199         MP_VERBOSE(arg, "Client disconnected\n");
200     }
201 }
202 
client_thread(void * p)203 static void *client_thread(void *p)
204 {
205     pthread_detach(pthread_self());
206 
207     struct client_arg *arg = p;
208     char buf[4096];
209     HANDLE wakeup_event = CreateEventW(NULL, TRUE, FALSE, NULL);
210     OVERLAPPED ol = { .hEvent = CreateEventW(NULL, TRUE, TRUE, NULL) };
211     bstr client_msg = { talloc_strdup(NULL, ""), 0 };
212     DWORD ioerr = 0;
213     DWORD r;
214 
215     mpthread_set_name(arg->client_name);
216 
217     arg->write_ol.hEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
218     if (!wakeup_event || !ol.hEvent || !arg->write_ol.hEvent) {
219         MP_ERR(arg, "Couldn't create events\n");
220         goto done;
221     }
222 
223     MP_VERBOSE(arg, "Client connected\n");
224 
225     mpv_set_wakeup_callback(arg->client, wakeup_cb, wakeup_event);
226 
227     // Do the first read operation on the pipe
228     if ((ioerr = async_read(arg->client_h, buf, 4096, &ol))) {
229         report_read_error(arg, ioerr);
230         goto done;
231     }
232 
233     while (1) {
234         HANDLE handles[] = { wakeup_event, ol.hEvent };
235         int n = WaitForMultipleObjects(2, handles, FALSE, 0);
236         if (n == WAIT_TIMEOUT)
237             n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
238 
239         switch (n) {
240         case WAIT_OBJECT_0: // wakeup_event
241             ResetEvent(wakeup_event);
242 
243             while (1) {
244                 mpv_event *event = mpv_wait_event(arg->client, 0);
245 
246                 if (event->event_id == MPV_EVENT_NONE)
247                     break;
248 
249                 if (event->event_id == MPV_EVENT_SHUTDOWN)
250                     goto done;
251 
252                 if (!arg->writable)
253                     continue;
254 
255                 char *event_msg = mp_json_encode_event(event);
256                 if (!event_msg) {
257                     MP_ERR(arg, "Encoding error\n");
258                     goto done;
259                 }
260 
261                 ipc_write_str(arg, event_msg);
262                 talloc_free(event_msg);
263             }
264 
265             break;
266         case WAIT_OBJECT_0 + 1: // ol.hEvent
267             // Complete the read operation on the pipe
268             if (!GetOverlappedResult(arg->client_h, &ol, &r, TRUE)) {
269                 report_read_error(arg, GetLastError());
270                 goto done;
271             }
272 
273             bstr_xappend(NULL, &client_msg, (bstr){buf, r});
274             while (bstrchr(client_msg, '\n') != -1) {
275                 char *reply_msg = mp_ipc_consume_next_command(arg->client,
276                     NULL, &client_msg);
277                 if (reply_msg && arg->writable)
278                     ipc_write_str(arg, reply_msg);
279                 talloc_free(reply_msg);
280             }
281 
282             // Begin the next read operation on the pipe
283             if ((ioerr = async_read(arg->client_h, buf, 4096, &ol))) {
284                 report_read_error(arg, ioerr);
285                 goto done;
286             }
287             break;
288         default:
289             MP_ERR(arg, "WaitForMultipleObjects failed\n");
290             goto done;
291         }
292     }
293 
294 done:
295     if (client_msg.len > 0)
296         MP_WARN(arg, "Ignoring unterminated command on disconnect.\n");
297 
298     if (CancelIoEx(arg->client_h, &ol) || GetLastError() != ERROR_NOT_FOUND)
299         GetOverlappedResult(arg->client_h, &ol, &(DWORD){0}, TRUE);
300     if (wakeup_event)
301         CloseHandle(wakeup_event);
302     if (ol.hEvent)
303         CloseHandle(ol.hEvent);
304     if (arg->write_ol.hEvent)
305         CloseHandle(arg->write_ol.hEvent);
306 
307     CloseHandle(arg->client_h);
308     mpv_destroy(arg->client);
309     talloc_free(arg);
310     return NULL;
311 }
312 
ipc_start_client(struct mp_ipc_ctx * ctx,struct client_arg * client)313 static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client)
314 {
315     client->client = mp_new_client(ctx->client_api, client->client_name),
316     client->log    = mp_client_get_log(client->client);
317 
318     pthread_t client_thr;
319     if (pthread_create(&client_thr, NULL, client_thread, client)) {
320         mpv_destroy(client->client);
321         CloseHandle(client->client_h);
322         talloc_free(client);
323     }
324 }
325 
ipc_start_client_json(struct mp_ipc_ctx * ctx,int id,HANDLE h)326 static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, HANDLE h)
327 {
328     struct client_arg *client = talloc_ptrtype(NULL, client);
329     *client = (struct client_arg){
330         .client_name = talloc_asprintf(client, "ipc-%d", id),
331         .client_h = h,
332         .writable = true,
333     };
334 
335     ipc_start_client(ctx, client);
336 }
337 
mp_ipc_start_anon_client(struct mp_ipc_ctx * ctx,struct mpv_handle * h,int out_fd[2])338 bool mp_ipc_start_anon_client(struct mp_ipc_ctx *ctx, struct mpv_handle *h,
339                               int out_fd[2])
340 {
341     return false;
342 }
343 
ipc_thread(void * p)344 static void *ipc_thread(void *p)
345 {
346     // Use PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE so message framing is
347     // maintained for message-mode clients, but byte-mode clients can still
348     // connect, send and receive data. This is the most compatible mode.
349     static const DWORD state =
350         PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT |
351         PIPE_REJECT_REMOTE_CLIENTS;
352     static const DWORD mode =
353         PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED;
354     static const DWORD bufsiz = 4096;
355 
356     struct mp_ipc_ctx *arg = p;
357     HANDLE server = INVALID_HANDLE_VALUE;
358     HANDLE client = INVALID_HANDLE_VALUE;
359     int client_num = 0;
360 
361     mpthread_set_name("ipc named pipe listener");
362     MP_VERBOSE(arg, "Starting IPC master\n");
363 
364     SECURITY_ATTRIBUTES sa = {
365         .nLength = sizeof sa,
366         .lpSecurityDescriptor = create_restricted_sd(),
367     };
368     if (!sa.lpSecurityDescriptor) {
369         MP_ERR(arg, "Couldn't create security descriptor");
370         goto done;
371     }
372 
373     OVERLAPPED ol = { .hEvent = CreateEventW(NULL, TRUE, TRUE, NULL) };
374     if (!ol.hEvent) {
375         MP_ERR(arg, "Couldn't create event");
376         goto done;
377     }
378 
379     server = CreateNamedPipeW(arg->path, mode | FILE_FLAG_FIRST_PIPE_INSTANCE,
380         state, PIPE_UNLIMITED_INSTANCES, bufsiz, bufsiz, 0, &sa);
381     if (server == INVALID_HANDLE_VALUE) {
382         MP_ERR(arg, "Couldn't create first pipe instance: %s\n",
383             mp_LastError_to_str());
384         goto done;
385     }
386 
387     MP_VERBOSE(arg, "Listening to IPC pipe.\n");
388 
389     while (1) {
390         DWORD err = ConnectNamedPipe(server, &ol) ? 0 : GetLastError();
391 
392         if (err == ERROR_IO_PENDING) {
393             int n = WaitForMultipleObjects(2, (HANDLE[]) {
394                 arg->death_event,
395                 ol.hEvent,
396             }, FALSE, INFINITE) - WAIT_OBJECT_0;
397 
398             switch (n) {
399             case 0:
400                 // Stop waiting for new clients
401                 CancelIo(server);
402                 GetOverlappedResult(server, &ol, &(DWORD){0}, TRUE);
403                 goto done;
404             case 1:
405                 // Complete the ConnectNamedPipe request
406                 err = GetOverlappedResult(server, &ol, &(DWORD){0}, TRUE)
407                     ? 0 : GetLastError();
408                 break;
409             default:
410                 MP_ERR(arg, "WaitForMultipleObjects failed\n");
411                 goto done;
412             }
413         }
414 
415         // ERROR_PIPE_CONNECTED is returned if a client connects before
416         // ConnectNamedPipe is called. ERROR_NO_DATA is returned if a client
417         // connects, (possibly) writes data and exits before ConnectNamedPipe
418         // is called. Both cases should be handled as normal connections.
419         if (err == ERROR_PIPE_CONNECTED || err == ERROR_NO_DATA)
420             err = 0;
421 
422         if (err) {
423             MP_ERR(arg, "ConnectNamedPipe failed: %s\n",
424                 mp_HRESULT_to_str(HRESULT_FROM_WIN32(err)));
425             goto done;
426         }
427 
428         // Create the next pipe instance before the client thread to avoid the
429         // theoretical race condition where the client thread immediately
430         // closes the handle and there are no active instances of the pipe
431         client = server;
432         server = CreateNamedPipeW(arg->path, mode, state,
433             PIPE_UNLIMITED_INSTANCES, bufsiz, bufsiz, 0, &sa);
434         if (server == INVALID_HANDLE_VALUE) {
435             MP_ERR(arg, "Couldn't create additional pipe instance: %s\n",
436                 mp_LastError_to_str());
437             goto done;
438         }
439 
440         ipc_start_client_json(arg, client_num++, client);
441         client = NULL;
442     }
443 
444 done:
445     if (sa.lpSecurityDescriptor)
446         LocalFree(sa.lpSecurityDescriptor);
447     if (client != INVALID_HANDLE_VALUE)
448         CloseHandle(client);
449     if (server != INVALID_HANDLE_VALUE)
450         CloseHandle(server);
451     if (ol.hEvent)
452         CloseHandle(ol.hEvent);
453     return NULL;
454 }
455 
mp_init_ipc(struct mp_client_api * client_api,struct mpv_global * global)456 struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
457                                struct mpv_global *global)
458 {
459     struct MPOpts *opts = mp_get_config_group(NULL, global, &mp_opt_root);
460 
461     struct mp_ipc_ctx *arg = talloc_ptrtype(NULL, arg);
462     *arg = (struct mp_ipc_ctx){
463         .log = mp_log_new(arg, global->log, "ipc"),
464         .client_api = client_api,
465     };
466 
467     if (!opts->ipc_path || !*opts->ipc_path)
468         goto out;
469 
470     // Ensure the path is a legal Win32 pipe name by prepending \\.\pipe\ if
471     // it's not already present. Qt's QLocalSocket uses the same logic, so
472     // cross-platform programs that use paths like /tmp/mpv-socket should just
473     // work. (Win32 converts this path to \Device\NamedPipe\tmp\mpv-socket)
474     if (!strncmp(opts->ipc_path, "\\\\.\\pipe\\", 9)) {
475         arg->path = mp_from_utf8(arg, opts->ipc_path);
476     } else {
477         char *path = talloc_asprintf(NULL, "\\\\.\\pipe\\%s", opts->ipc_path);
478         arg->path = mp_from_utf8(arg, path);
479         talloc_free(path);
480     }
481 
482     if (!(arg->death_event = CreateEventW(NULL, TRUE, FALSE, NULL)))
483         goto out;
484 
485     if (pthread_create(&arg->thread, NULL, ipc_thread, arg))
486         goto out;
487 
488     talloc_free(opts);
489     return arg;
490 
491 out:
492     if (arg->death_event)
493         CloseHandle(arg->death_event);
494     talloc_free(arg);
495     talloc_free(opts);
496     return NULL;
497 }
498 
mp_uninit_ipc(struct mp_ipc_ctx * arg)499 void mp_uninit_ipc(struct mp_ipc_ctx *arg)
500 {
501     if (!arg)
502         return;
503 
504     SetEvent(arg->death_event);
505     pthread_join(arg->thread, NULL);
506 
507     CloseHandle(arg->death_event);
508     talloc_free(arg);
509 }
510