Repository navigation
fix(x11): read the server's replies only within their bytes - #497
Conversation
siracozmen01
left a comment
There was a problem hiding this comment.
Review at 2193dd3: changes needed (2). I read the code and did not run it. CI was still pending.
1. The message box still reads a QueryFont reply past the bytes libxcb holds. Measure in src/x11_message_box.c (line 243 at this head) takes api->queryFontReply(...) unchecked and reads metrics->font_ascent and metrics->font_descent. They sit at bytes 52 to 55 of xcb_query_font_reply_t. libxcb holds 32 bytes and 4 per unit of the reply's length, whatever the request was.
- Concrete case: a server answers QueryFont with length 0. libxcb hands over a 32-byte buffer, and
Showreads 20 to 24 bytes past it. max_bounds.character_widthat bytes 28 and 29 is within the 32.- This is the same fault as #496, and
docs/internals/x11.mdnow says "every reply and event is read through x11_reply.c first". - A check that the reply holds
sizeof(xcb_query_font_reply_t)closes it, released and taken as refused like the others; the box already falls back to 11 and 2. Please add a case for it totest_x11_reply. - The other fixed-size replies the backend reads (InternAtom, TranslateCoordinates, GrabPointer, GetSelectionOwner, the version queries, PerClientFlags, RandR's CRTC info and output info) only use fields within the first 32 bytes, so they are safe as they are.
2. A property or output property of 2 GiB or more is kept, and its length still turns negative. mwinX11CheckProperty and mwinX11CheckOutputProperty check the value against the bytes held, which is right. They do not check that value_len * format / 8 fits the int that xcb_get_property_value_length and xcb_randr_get_output_property_data_length return.
- Concrete case: a server answers the drop's GetProperty with format 8,
value_len0x80000000 and a length of 0x20000000 units, so it really sends 2 GiB. - The check keeps it.
getPropertyValueLengthreturnsINT_MIN.Takeinsrc/x11_drop.c:292casts that tosize_t, about 1.8e19.mwinGatherUriListthen scans the 2 GiB buffer by that length (memchr(list + start, '\n', length - start)) and reads past it when there is no newline. - The EDID path does the same through
(size_t)api->randrOutputPropertyDataLength(edid). - The test "a property's count past INT_MAX bytes" passes for another reason: its reply holds 4 bytes, so it is refused as short, never for the
INT_MAXcount. The body names this case as fixed. - Refuse a value of more than
INT_MAXbytes in both checks. Then make the test's reply claim enough length (setlength, no need to allocate it), so that only the new check refuses it.
What you asked about.
- Every count read through a check. Every reply with a list or a string the backend reads now passes a check before use: GetProperty in all six places, AtomName, OutputProperty, the screen resources, the monitors, the picture formats, ListInputDevices and XIQueryDevice. So do the XInput events
OnXihandles. RAW_MOTION is checked through the raw values, which lie past the mask and the accelerated values. The button, motion and touch events are checked through their axis values. DEVICE_CHANGED'sreasonis within its fixed part. HIERARCHY reads no fields. The setup goes throughmwinX11ScreenOf. The libxcb device and class walkers andxcb_screen_nextare no longer loaded, and the walks go by checked lengths. A class of length 0 is refused (len * 4 >= FieldsOf(type)), so the walk always moves on. Finding 1 is the one read left outside. - Arithmetic.
mwinX11Holdscomparescount <= left / sizeafter checkingat - from <= holds, so it cannot wrap. That is the same boundckd_mulgives. The byte counts areuint32_ttimes 4 inuint64_t, and the other sums areuint16_tanduint8_tcounts times struct sizes, far from any limit. - Refused, not asserted. Every check releases the reply and returns NULL, or passes the event over, and each caller's NULL path runs. There is no assert on server data.
ReadDevicesno longer touches a NULL reply. The message box answersmwin_errorPlatformfor a setup without a whole first screen. - Merge and body. Against main,
CMakeLists.txtonly adds thex11_replytest. The body has Problem, Change and Verification, and no line starts at column 0 with a name and(.
siracozmen01
left a comment
There was a problem hiding this comment.
Re-review at c9ea642: ready on the code. One CI failure is unexplained, below. I read the code and did not run it.
Both findings are fixed.
- QueryFont.
mwinX11CheckFontkeeps a reply only when it holds the wholexcb_query_font_reply_t(InReply(reply, reply, 1, sizeof(*reply)), 60 bytes), andMeasuretakes the reply through it, sofont_ascentandfont_descentare always within the bytes held. A refused reply falls back to 6, 11 and 2 as before. The test covers a 60-byte reply (kept, ascent read back), a 32-byte one, one of 52 that ends before the ascent, and NULL. - Property sizes.
FitsIntrefusescount * format / 8pastINT_MAX, withckd_mulinsize_t, which a 32-bit target also refuses on overflow. Both checks run it beforeInReply. Every caller now takes its size frommwinX11PropertyBytesormwinX11OutputPropertyBytes: drop's type list and data,_NET_WM_STATE, the clipboard read, Xft.dpi, the WM check, the EDID and VRR. Theintaccessors are no longer loaded, so a call left behind would not compile. The loops in drop andReadModenow count insize_t. The clipboard's(uint32_t)cast of a size of at mostINT_MAXis exact. - The tests refuse for the right reason now.
Huge()claims 2 GiB in the length field, soInReplyholds the value, and onlyFitsIntrefuses: format 32 with 0x20000000 items, and format 8 with 0x80000000. The old short-reply case stays, renamed "past its bytes". - Every other
getPropertyReplyin the X11 sources goes throughmwinX11CheckProperty.x11_clipboard.c,x11_chrome.candx11_resources.cread only fixed-size replies or none. if(TARGET test_x11_reply)covers the shared build, wheremaul_add_test(... WHITEBOX)makes no target.
CI. ios-simulator failed after 43 s in run 42483229020 (pull_request, this head). Its log is not available until the run ends, so I could not read why. Nothing in this pull request builds for iOS, but please check that it is the runner and not the CMake change before this merges.
Problem
On X11, the library read the lists and strings in the X server's replies and events by the counts the server put beside them, and never checked those counts against the bytes the reply or event held. libxcb sizes a reply's buffer by its length field alone (32 bytes and 4 per unit, in
read_packet), and its accessors find each part from fields such asvalue_len,num_items,nOutputorname_len. A server that sent a count larger than what followed made the library read past the end of that buffer. A property'svalue_len * format / 8could also passINT_MAX, andsrc/x11_drop.ccast the negativeintthatxcb_get_property_value_lengththen returns tosize_t(#496).While there:
ReadDevicesinsrc/x11_input.ccalledxcb_input_xi_query_device_infos_iteratoron the reply before checking it for NULL, so a failedXIQueryDevicecrashed. The message box took the first screen of the setup without checking that the setup has one.Change
src/x11_reply.cholds one check,mwinX11Holds: whethercountitems ofsizebytes at a part lie within the bytes held from the reply's start, with arithmetic that cannot wrap. The bytes held are 32 and 4 per unit oflengthfor a reply, the same after the full sequence libxcb adds for a generic event, and 8 and 4 per unit for the setup.mwin_errorPlatform.num_keys, not from the class'slen, so a class whose fields disagree with its length, or one of a type libxcb does not know, sends them past the reply even where every length is right. The device infos and their classes are now walked by the length each gives, as the protocol defines it. Each class must be as long as the fields of its type the backend reads (valuator, scroll, touch). The six accessors and steps of libxcb that walk them, andxcb_screen_next, are no longer loaded.size_tfrom the count and the format the reply gives (mwinX11PropertyBytes,mwinX11OutputPropertyBytes), never through libxcb'sxcb_get_property_value_lengthandxcb_randr_get_output_property_data_length, which return anint, and a value of more thanINT_MAXbytes is refused with the rest of a malformed reply. Before, a reply that claimed 2 GiB or more passed the bounds check and its length became a negativeint, whichsrc/x11_drop.cturned into a hugesize_tformwinGatherUriList, and the EDID path the same. Both accessors are gone from the function table.mwinX11CheckFont): it must hold its fields whole, up to the ascent and descent at bytes 52 to 55, so a reply of no length is no longer read past its 32 bytes.test_x11_replyis a white-box test, which a shared build does not make, so the lines that set its include directories and skip code are kept to builds that have it.docs/internals/x11.mdsays how replies are read.The 17 accessors that give a part's count, and the font reply's fields read past its fixed part, with their uses at 8d02e25, and how each is read now:
xcb_get_property_value_lengthsrc/backend_x11.c:165,src/backend_x11.c:177,src/x11_clipboard_read.c:221,src/x11_drop.c:68,src/x11_drop.c:289,src/x11_window.c:665mwinX11CheckPropertyon everyGetPropertyreply, the size frommwinX11PropertyBytes; the accessor is no longer usedxcb_get_atom_name_name_lengthsrc/x11_output.c:24mwinX11CheckAtomNamexcb_randr_get_output_property_data_lengthsrc/x11_output.c:132(the EDID),src/x11_output.c:145mwinX11CheckOutputProperty, the size frommwinX11OutputPropertyBytes; the accessor is no longer usedxcb_randr_get_screen_resources_current_modes_lengthsrc/x11_output.c:109mwinX11CheckResourcesxcb_randr_get_monitors_monitors_iteratorsrc/x11_output.c:209,src/x11_output.c:215mwinX11CheckMonitors, each monitor before its stepxcb_randr_monitor_info_outputs_lengthsrc/x11_output.c:234xcb_render_query_pict_formats_formats_lengthsrc/x11_cursor.c:129mwinX11CheckFormatsxcb_input_list_input_devices_devices_lengthsrc/x11_input.c:310mwinX11CheckInputDevicesxcb_input_xi_query_device_infos_iteratorsrc/x11_input.c:405mwinX11CheckDevices, thenmwinX11DevicesOfxcb_input_xi_device_info_classes_iteratorsrc/x11_input.c:341,src/x11_input.c:366,src/x11_input.c:380mwinX11CheckDevices, thenmwinX11ClassesOfxcb_input_xi_device_info_name_lengthsrc/x11_input.c:362mwinX11CheckDevices, thenname_lenxcb_input_button_press_valuator_mask_lengthsrc/x11_input.c:560,src/x11_input.c:584,src/x11_input.c:658mwinX11XiEventWholeon every XInput eventxcb_input_button_press_axisvalues_lengthsrc/x11_input.c:561,src/x11_input.c:585,src/x11_input.c:659xcb_input_raw_button_press_valuator_mask_lengthsrc/x11_input.c:512xcb_input_raw_button_press_axisvalues_raw_lengthsrc/x11_input.c:514xcb_setup_roots_iteratorsrc/backend_x11.c:244,src/x11_message_box.c:284mwinX11ScreenOfxcb_query_font_reply_t'sfont_ascentandfont_descent, past its fixed partsrc/x11_message_box.c:247,src/x11_message_box.c:248mwinX11CheckFontxcb_get_maximum_request_lengthsrc/x11_icon.c:30Verification
test/test_x11_reply.cloads libxcb's own accessors throughmwinLoadX11, which needs no display, and hands the checks replies, events and setups built by hand. For each family (a property, an atom's name, an output property, the screen resources' modes, the monitors and their outputs, the picture formats, the input devices, XIQueryDevice's infos, names and classes, device and raw XInput events, and the setup's screens, depths and visuals), it builds one whose parts fit and one whose count passes its bytes, and checks that only the first is kept. It also covers a property count pastINT_MAX, a format no property has, a class shorter than its type's fields or of no length, and the walk past a class of a type libxcb does not know. Under ASan and UBSan, a malformed reply's release is seen by the leak check, and a read past a buffer would be seen too: an early version of the device check that read a missing info's name length was caught that way.linux-clang-20-asanbuilds the library andtest_x11_replywith no error and no warning, andx11_replypasses.test_x11_replyalso builds, for each property kind, a short reply whose length field claims 2 GiB, so that it passes the bounds check, with a value of 2 GiB. It also builds QueryFont replies of 32 and 52 bytes. With theINT_MAXrefusal taken out ofsrc/x11_reply.c, the two property checks fail and nothing else; withmwinX11CheckFontpassing every reply, the two short font checks fail and nothing else. With both in place,x11_replypasses.linux-gcc-14andweb-wasiin the CI image,linux-clang-20-asan,linux-shared(whose configure failed CI'schecksjob at 2193dd3),linux-minimal, the fuzz tree, MinGW (through clang-20) andandroid-emulator.tools/check.sh prpassed in the CI image at 2d8cfe8: every test, among them the 21 X11 tests that drive the build's own X server, clang-tidy clean onsrc/x11_reply.c, and 10 seconds of every fuzz target.fuzz_x11_selectionandfuzz_x11_dropran 2 minutes each from their committed seeds at 2d8cfe8 with no finding (about 101,000 and 105,000 runs), after 5 minutes each with no finding at 2193dd3.Closes #496