Skip to content

Commit 45626ab

Browse files
committed
fix: do not tile utility toplevels and prevent IPC crashes due to json parsing
1 parent d9bb23a commit 45626ab

5 files changed

Lines changed: 200 additions & 51 deletions

File tree

src/BSPTree.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ void BSPTree::apply_geometries(BSPNode *node) {
263263
return;
264264

265265
if (node->is_leaf() && node->toplevel) {
266-
node->toplevel->set_position_size(node->geometry);
266+
if (!node->toplevel->maximized() && !node->toplevel->fullscreen())
267+
node->toplevel->set_position_size(node->geometry);
267268
return;
268269
}
269270

src/IPC.cpp

Lines changed: 171 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,130 @@
1010
#include <sys/socket.h>
1111
using json = nlohmann::ordered_json;
1212

13+
// brain damage (this may not be needed but whatever)
14+
static std::string sanitize_for_json(std::string_view input) {
15+
std::string result;
16+
result.reserve(input.size());
17+
18+
for (size_t i = 0; i < input.size();) {
19+
unsigned char c = input[i];
20+
21+
// ASCII (0x00-0x7F)
22+
if (c <= 0x7F) {
23+
result += c;
24+
i++;
25+
}
26+
// 2-byte UTF-8 (0xC0-0xDF)
27+
else if ((c & 0xE0) == 0xC0 && i + 1 < input.size()) {
28+
unsigned char c2 = input[i + 1];
29+
if ((c2 & 0xC0) == 0x80) {
30+
result += c;
31+
result += c2;
32+
i += 2;
33+
} else {
34+
i++;
35+
}
36+
}
37+
// 3-byte UTF-8 (0xE0-0xEF)
38+
else if ((c & 0xF0) == 0xE0 && i + 2 < input.size()) {
39+
unsigned char c2 = input[i + 1];
40+
unsigned char c3 = input[i + 2];
41+
if ((c2 & 0xC0) == 0x80 && (c3 & 0xC0) == 0x80) {
42+
result += c;
43+
result += c2;
44+
result += c3;
45+
i += 3;
46+
} else {
47+
i++;
48+
}
49+
}
50+
// 4-byte UTF-8 (0xF0-0xF7)
51+
else if ((c & 0xF8) == 0xF0 && i + 3 < input.size()) {
52+
unsigned char c2 = input[i + 1];
53+
unsigned char c3 = input[i + 2];
54+
unsigned char c4 = input[i + 3];
55+
if ((c2 & 0xC0) == 0x80 && (c3 & 0xC0) == 0x80 &&
56+
(c4 & 0xC0) == 0x80) {
57+
result += c;
58+
result += c2;
59+
result += c3;
60+
result += c4;
61+
i += 4;
62+
} else {
63+
i++;
64+
}
65+
} else {
66+
i++;
67+
}
68+
}
69+
70+
return result;
71+
}
72+
73+
// brain damage
74+
static std::string sanitize_for_json(const char *str) {
75+
if (!str)
76+
return "";
77+
78+
std::string result;
79+
size_t i = 0;
80+
81+
while (str[i] != '\0') {
82+
unsigned char c = str[i];
83+
// ASCII (0x00-0x7F)
84+
if (c <= 0x7F) {
85+
result += c;
86+
i++;
87+
}
88+
// 2-byte UTF-8 (0xC0-0xDF)
89+
else if ((c & 0xE0) == 0xC0 && str[i + 1] != '\0') {
90+
unsigned char c2 = str[i + 1];
91+
if ((c2 & 0xC0) == 0x80) {
92+
result += c;
93+
result += c2;
94+
i += 2;
95+
} else {
96+
i++;
97+
}
98+
}
99+
// 3-byte UTF-8 (0xE0-0xEF)
100+
else if ((c & 0xF0) == 0xE0 && str[i + 1] != '\0' &&
101+
str[i + 2] != '\0') {
102+
unsigned char c2 = str[i + 1];
103+
unsigned char c3 = str[i + 2];
104+
if ((c2 & 0xC0) == 0x80 && (c3 & 0xC0) == 0x80) {
105+
result += c;
106+
result += c2;
107+
result += c3;
108+
i += 3;
109+
} else {
110+
i++;
111+
}
112+
}
113+
// 4-byte UTF-8 (0xF0-0xF7)
114+
else if ((c & 0xF8) == 0xF0 && str[i + 1] != '\0' &&
115+
str[i + 2] != '\0' && str[i + 3] != '\0') {
116+
unsigned char c2 = str[i + 1];
117+
unsigned char c3 = str[i + 2];
118+
unsigned char c4 = str[i + 3];
119+
if ((c2 & 0xC0) == 0x80 && (c3 & 0xC0) == 0x80 &&
120+
(c4 & 0xC0) == 0x80) {
121+
result += c;
122+
result += c2;
123+
result += c3;
124+
result += c4;
125+
i += 4;
126+
} else {
127+
i++;
128+
}
129+
} else {
130+
i++;
131+
}
132+
}
133+
134+
return result;
135+
}
136+
13137
IPC::IPC(Server *server, std::string sock_path)
14138
: server(server), path(sock_path) {
15139
// create file descriptor
@@ -309,7 +433,7 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
309433
};
310434

311435
// outputs are distinguished by name
312-
j[o->name] = {
436+
j[sanitize_for_json(o->name)] = {
313437
{"enabled", o->enabled},
314438
{"focused", output == server->focused_output()},
315439
{"workspace", output->get_active()->num},
@@ -342,16 +466,20 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
342466
// below values may be null (e.g. virtual outputs)
343467
// they are generally set on physical displays though
344468
if (o->description)
345-
j[o->name]["description"] = o->description;
469+
j[sanitize_for_json(o->name)]["description"] =
470+
sanitize_for_json(o->description);
346471

347472
if (o->make)
348-
j[o->name]["make"] = o->make;
473+
j[sanitize_for_json(o->name)]["make"] =
474+
sanitize_for_json(o->make);
349475

350476
if (o->model)
351-
j[o->name]["model"] = o->model;
477+
j[sanitize_for_json(o->name)]["model"] =
478+
sanitize_for_json(o->model);
352479

353480
if (o->serial)
354-
j[o->name]["serial"] = o->serial;
481+
j[sanitize_for_json(o->name)]["serial"] =
482+
sanitize_for_json(o->serial);
355483
}
356484
break;
357485
}
@@ -361,11 +489,11 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
361489
wl_list_for_each_safe(workspace, tmp1,
362490
&server->workspace_manager->workspaces, link) {
363491
int i = 0;
364-
j[workspace->output->wlr_output->name][workspace->num] =
365-
json::array();
492+
j[sanitize_for_json(workspace->output->wlr_output->name)]
493+
[workspace->num] = json::array();
366494
wl_list_for_each_safe(toplevel, tmp2, &workspace->toplevels, link)
367-
j[workspace->output->wlr_output->name][workspace->num - 1]
368-
[i++] = string_format("%p", toplevel);
495+
j[sanitize_for_json(workspace->output->wlr_output->name)]
496+
[workspace->num - 1][i++] = string_format("%p", toplevel);
369497
}
370498
break;
371499
}
@@ -379,7 +507,7 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
379507
link) {
380508
// output modes are defined as a size, refresh rate and
381509
// preferred status - the selected mode is the current mode
382-
j[output->wlr_output->name][i++] = {
510+
j[sanitize_for_json(output->wlr_output->name)][i++] = {
383511
{"refresh", mode->refresh / 1000.0},
384512
{"width", mode->width},
385513
{"height", mode->height},
@@ -496,12 +624,13 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
496624
// toplevels are indexed by their pointer as title is
497625
// non-unique
498626
j[string_format("%p", t)] = {
499-
{"title", t->get_title()},
500-
{"class", t->get_app_id()},
501-
{"tag", t->tag},
502-
{"foreign", t->ext_foreign_handle
503-
? t->ext_foreign_handle->identifier
504-
: "None"},
627+
{"title", sanitize_for_json(t->get_title())},
628+
{"class", sanitize_for_json(t->get_app_id())},
629+
{"tag", sanitize_for_json(t->tag)},
630+
{"foreign",
631+
t->ext_foreign_handle && t->ext_foreign_handle->identifier
632+
? sanitize_for_json(t->ext_foreign_handle->identifier)
633+
: "None"},
505634
{"x", t->geometry.x},
506635
{"y", t->geometry.y},
507636
{"width", t->geometry.width},
@@ -525,12 +654,13 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
525654

526655
if (const Toplevel *t = w->active_toplevel) {
527656
j = {
528-
{"title", t->get_title()},
529-
{"class", t->get_app_id()},
530-
{"tag", t->tag},
531-
{"foreign", t->ext_foreign_handle
532-
? t->ext_foreign_handle->identifier
533-
: "None"},
657+
{"title", sanitize_for_json(t->get_title())},
658+
{"class", sanitize_for_json(t->get_app_id())},
659+
{"tag", sanitize_for_json(t->tag)},
660+
{"foreign",
661+
t->ext_foreign_handle && t->ext_foreign_handle->identifier
662+
? sanitize_for_json(t->ext_foreign_handle->identifier)
663+
: "None"},
534664
{"x", t->geometry.x},
535665
{"y", t->geometry.y},
536666
{"width", t->geometry.width},
@@ -547,10 +677,10 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
547677
}
548678
case IPC_KEYBOARD_LIST: {
549679
// just return the keyboard config
550-
j = {{"layout", server->config->keyboard_layout},
551-
{"model", server->config->keyboard_model},
552-
{"variant", server->config->keyboard_variant},
553-
{"options", server->config->keyboard_options},
680+
j = {{"layout", sanitize_for_json(server->config->keyboard_layout)},
681+
{"model", sanitize_for_json(server->config->keyboard_model)},
682+
{"variant", sanitize_for_json(server->config->keyboard_variant)},
683+
{"options", sanitize_for_json(server->config->keyboard_options)},
554684
{"repeat_rate", server->config->repeat_rate},
555685
{"repeat_delay", server->config->repeat_delay}};
556686
break;
@@ -586,7 +716,10 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
586716

587717
// add to list of devices
588718
j[i++] = {
589-
{"name", keyboard->wlr_keyboard->base.name},
719+
{"name",
720+
keyboard->wlr_keyboard->base.name
721+
? sanitize_for_json(keyboard->wlr_keyboard->base.name)
722+
: "Unknown"},
590723
{"type", type},
591724
};
592725
}
@@ -607,15 +740,15 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
607740
layout_idx++) {
608741

609742
// this long name is nicer for clients
610-
std::string layout_name =
611-
xkb_keymap_layout_get_name(keymap, layout_idx);
743+
std::string layout_name = sanitize_for_json(
744+
xkb_keymap_layout_get_name(keymap, layout_idx));
612745

613746
// can be -1 if invalid
614747
int layout_enabled = xkb_state_layout_index_is_active(
615748
state, layout_idx, XKB_STATE_LAYOUT_EFFECTIVE);
616749

617750
j[keyboard->wlr_keyboard->base.name
618-
? keyboard->wlr_keyboard->base.name
751+
? sanitize_for_json(keyboard->wlr_keyboard->base.name)
619752
: "default"][layout_idx] = {
620753
{"enabled", layout_enabled},
621754
{"layout", layout_name},
@@ -637,7 +770,7 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
637770
// get the name of the keysym
638771
char buffer[255];
639772
xkb_keysym_get_name(bind.sym, buffer, 255);
640-
std::string name(buffer);
773+
std::string name = sanitize_for_json(buffer);
641774

642775
// handle mouse binds
643776
if (bind.sym >= 0x20000000 + 272 && bind.sym <= 0x20000000 + 276)
@@ -708,8 +841,9 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
708841
modifiers += MODIFIERS[j] + " ";
709842

710843
// get the name of the keysym
711-
std::string name;
712-
xkb_keysym_get_name(bind.sym, name.data(), sizeof(name));
844+
char buffer[255];
845+
xkb_keysym_get_name(bind.sym, buffer, 255);
846+
std::string name = sanitize_for_json(buffer);
713847

714848
// display
715849
j = {
@@ -728,15 +862,15 @@ json IPC::handle_command(const IPCMessage message, const std::string &data) {
728862
case IPC_RULE_LIST: {
729863
std::vector<WindowRule *> rules = server->config->window_rules;
730864
for (unsigned long i = 0; i != rules.size(); ++i) {
731-
json res = {{"title", rules[i]->title},
732-
{"class", rules[i]->class_},
733-
{"tag", rules[i]->tag}};
865+
json res = {{"title", sanitize_for_json(rules[i]->title)},
866+
{"class", sanitize_for_json(rules[i]->class_)},
867+
{"tag", sanitize_for_json(rules[i]->tag)}};
734868

735869
if (rules[i]->workspace)
736870
res["workspace"] = rules[i]->workspace;
737871

738872
if (!rules[i]->output.empty())
739-
res["output"] = rules[i]->output;
873+
res["output"] = sanitize_for_json(rules[i]->output);
740874

741875
if (rules[i]->toplevel_state)
742876
switch (*rules[i]->toplevel_state) {

src/Popup.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ Popup::Popup(wlr_xdg_popup *xdg_popup, wlr_scene_tree *parent_tree,
99
xdg_popup->base->data = parent_tree;
1010

1111
// set capture parent if provided
12-
if (image_capture_parent)
13-
image_capture_tree =
14-
wlr_scene_xdg_surface_create(image_capture_parent, xdg_popup->base);
12+
if (image_capture_parent) {
13+
image_capture_tree = wlr_scene_tree_create(image_capture_parent);
14+
15+
// manually create surface for image capture
16+
wlr_scene_surface_create(image_capture_tree, xdg_popup->base->surface);
17+
}
1518

1619
// xdg_popup_commit
1720
commit.notify = [](wl_listener *listener, [[maybe_unused]] void *data) {

0 commit comments

Comments
 (0)