Bug 482137 - Regression: Filesystem/Clipboard not shared anymore
Summary: Regression: Filesystem/Clipboard not shared anymore
Status: RESOLVED FIXED
Alias: None
Product: krdc
Classification: Applications
Component: RDP (show other bugs)
Version: 24.02.0
Platform: Neon Linux
: NOR normal
Target Milestone: ---
Assignee: Urs Wolfer
URL:
Keywords: qt6
: 484079 484607 (view as bug list)
Depends on:
Blocks:
 
Reported: 2024-03-01 11:10 UTC by Arek Guzinski
Modified: 2024-10-28 10:10 UTC (History)
9 users (show)

See Also:
Latest Commit:
Version Fixed In: 24.08.3
Sentry Crash Report:


Attachments
Test case 1 (290 bytes, text/x-c++src)
2024-10-26 09:38 UTC, Marcel Hasler
Details
Test case 2 (1.13 KB, text/x-c++src)
2024-10-26 09:38 UTC, Marcel Hasler
Details
Test case 3 (788 bytes, text/x-c++src)
2024-10-26 09:39 UTC, Marcel Hasler
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Arek Guzinski 2024-03-01 11:10:23 UTC
SUMMARY
after upgrading to 24.02, clipboard and filesystem are not shared anymore.
Downgrading back to 23.08.5 fixes it.


STEPS TO REPRODUCE
1. connect to a windows machine via rdp
2. try to copy/paste text
3. access network drive //tsclient to copy files

OBSERVED RESULT
clipboard is not shared between machines - both sides only paste their own last copied text.
//tsclient exists but has no directories to copy files to/from

EXPECTED RESULT
text copied on host or server can be pasted in either machine.
//tsclient on windows contains a directory that maps to a path on my linux machine

SOFTWARE/OS VERSIONS
Windows: 10
Operating System: KDE neon 6.0
KDE Plasma Version: 6.0.0
KDE Frameworks Version: 6.0.0
Qt Version: 6.6.2
Kernel Version: 6.5.0-21-generic (64-bit)
Graphics Platform: Wayland
Processors: 4 × Intel® Core™ i5-6500 CPU @ 3.20GHz
Memory: 15,5 GiB of RAM
Graphics Processor: NVIDIA GeForce GTX 1060 6GB/PCIe/SSE2

ADDITIONAL INFORMATION
I'm also missing the extra options text field in the host configuration window, but that's not critical (for me)
Comment 1 Arjen Hiemstra 2024-03-22 09:55:00 UTC
*** Bug 484079 has been marked as a duplicate of this bug. ***
Comment 2 Ninjoe 2024-04-12 16:11:37 UTC
I have this problem on Fedora 40 too.

KDE Plasma: 6.0.3
KRDC: krdc-24.02.1-1.fc40

And I really miss Extra Options, since it allow me to enable Dynamic Resolution (/dynamic-resolution).
Also, it would be nice if the GUI has an option for Dynamic Resolution.

Meanwhile, I switch to KRDC 23.08 from Flathub.
Comment 3 Fabio 2024-05-21 20:14:10 UTC
Clipboard has been fixed in current development version (see BUG 484666), filesystem sharing is still missing.
Comment 4 Fabio 2024-05-21 20:20:07 UTC
*** Bug 484607 has been marked as a duplicate of this bug. ***
Comment 5 Ninjoe 2024-07-03 09:00:04 UTC
As of 24.05, folder sharing still doesn't work.
Comment 6 Marcel Hasler 2024-10-24 19:19:10 UTC
(In reply to Ninjoe from comment #5)
> As of 24.05, folder sharing still doesn't work.

This is a bug in RdpSession::start().

Actually the code is wrong in a few ways:
1. freerdp_client_add_device_channel() expects the number of strings passed, which should be >= 2 for the "drive" channel. Passing 1 will cause the function to abort (see https://github.com/FreeRDP/FreeRDP/blob/5b14b7cbdd36414f1838047f21502654bd32ebb1/client/common/cmdline.c#L539)
2. strdup is used to copy the string "drive", but the pointer returned is never freed, causing a memory leak (see https://en.cppreference.com/w/c/experimental/dynamic/strdup)
3. The pointer returned by m_preferences->shareMedia().toLocal8Bit().data() is no longer valid by the time it's passed to freerdp_client_add_device_channel, since it references a temporary object that no longer exists at this point

I would suggest the following implementation which I have just tested and that works for me:

    if (!m_preferences->shareMedia().isEmpty()) {
        QByteArray name = "drive";
        QByteArray value = m_preferences->shareMedia().toLocal8Bit();

        char *params[2] = { name.data(), value.data() };
        freerdp_client_add_device_channel(settings, 2, params);
    }
Comment 7 Fabio 2024-10-25 14:27:05 UTC
(In reply to Marcel Hasler from comment #6)
> This is a bug in RdpSession::start().

I confirm your  patch fixes the issue. A small variant has been submitted here: https://invent.kde.org/network/krdc/-/merge_requests/114

> 3. The pointer returned by m_preferences->shareMedia().toLocal8Bit().data() 
> is no longer valid by the time it's passed to
> freerdp_client_add_device_channel, since it references a temporary object
> that no longer exists at this point

FreeRDP internally calls _strdup on that parameter in https://github.com/FreeRDP/FreeRDP/blob/stable-2.0/client/common/cmdline.c#L185 , so it's safe to use
Comment 8 Fabio 2024-10-25 14:33:47 UTC
Fix has been merged and backported; will be available in 24.08.3, expected release on november 7
Comment 9 Marcel Hasler 2024-10-25 16:28:38 UTC
(In reply to Fabio from comment #7)
> (In reply to Marcel Hasler from comment #6)
> > This is a bug in RdpSession::start().
> 
> I confirm your  patch fixes the issue. A small variant has been submitted
> here: https://invent.kde.org/network/krdc/-/merge_requests/114
> 
> > 3. The pointer returned by m_preferences->shareMedia().toLocal8Bit().data() 
> > is no longer valid by the time it's passed to
> > freerdp_client_add_device_channel, since it references a temporary object
> > that no longer exists at this point
> 
> FreeRDP internally calls _strdup on that parameter in
> https://github.com/FreeRDP/FreeRDP/blob/stable-2.0/client/common/cmdline.
> c#L185 , so it's safe to use

I don't think this is really true. I actually wrote a small test out of curiosity and found that the data indeed remains accessible, even if the QByteArray has been destroyed at that point (I would have expected a segfault). Apparently Qt doesn't immediately free the memory when a QByteArray goes out of scope but keeps it around for reuse. But here's a simple example to show that you cannot rely on the data remaining valid:

#include <QString>
#include <iostream>

auto main() -> int
{
    char* data1 = nullptr;

    {
        QString test1 = "Test 1";
        data1 = test1.toLocal8Bit().data();
    }

    QString test2 = "Test 2";
    char* data2 = test2.toLocal8Bit().data();
    Q_UNUSED(data2);

    std::cout << data1 << std::endl;

    return 0;
}
Comment 10 Fabio 2024-10-25 17:03:45 UTC
(In reply to Marcel Hasler from comment #9)
> But here's a simple example to show that you cannot rely on the data remaining valid:

I know that when the local function variable goes out of scope it's not safe to use anymore.
FreeRDP creates an internal duplicate of the "path" string using _strdup():

> if ((!isPath && !isSpecial) || !(drive->Path = _strdup(path)))

From that point FreeRDP will use its internal copy of the string stored in drive->Path.
So, even if our variable gets deleted, it doesn't matter.
Comment 11 Marcel Hasler 2024-10-25 18:36:48 UTC
(In reply to Fabio from comment #10)
> (In reply to Marcel Hasler from comment #9)
> > But here's a simple example to show that you cannot rely on the data remaining valid:
> 
> I know that when the local function variable goes out of scope it's not safe
> to use anymore.
> FreeRDP creates an internal duplicate of the "path" string using _strdup():
> 
> > if ((!isPath && !isSpecial) || !(drive->Path = _strdup(path)))
> 
> From that point FreeRDP will use its internal copy of the string stored in
> drive->Path.
> So, even if our variable gets deleted, it doesn't matter.

The point I was trying to make is that by the time freerdp actually calls _strdup, both temporary QByteArray instances have already been destroyed and the pointers are therefore no longer valid. It just *happens* to work because the data hasn't yet been freed or reused/modified in the meantime, but that doesn't mean the code is correct or that it will continue to work in the future. The data just *happens* to still be available, but really you are relying on undocumented behavior here. In fact, the Qt folks could modify their internal implementation at any point (even from a patch version to the next), causing your code to crash or misbehave in other ways.

As I see it, the solution is really simple here, just create two local variables and avoid any potential trouble in the future.
Comment 12 Fabio 2024-10-26 06:15:56 UTC
(In reply to Marcel Hasler from comment #11)
> The point I was trying to make is that by the time freerdp actually calls
> _strdup, both temporary QByteArray instances have already been destroyed and
> the pointers are therefore no longer valid.

It's true in your code example in https://bugs.kde.org/show_bug.cgi?id=482137#c9 because of the curly braces specifically added to declare data1 inside a different scope.
It's not true in the code used in the fix for krdc: https://invent.kde.org/network/krdc/-/merge_requests/114/diffs
Comment 13 Marcel Hasler 2024-10-26 09:37:44 UTC
(In reply to Fabio from comment #12)
> (In reply to Marcel Hasler from comment #11)
> > The point I was trying to make is that by the time freerdp actually calls
> > _strdup, both temporary QByteArray instances have already been destroyed and
> > the pointers are therefore no longer valid.
> 
> It's true in your code example in
> https://bugs.kde.org/show_bug.cgi?id=482137#c9 because of the curly braces
> specifically added to declare data1 inside a different scope.
> It's not true in the code used in the fix for krdc:
> https://invent.kde.org/network/krdc/-/merge_requests/114/diffs

Yes it is. Remove the curly braces and see for yourself.
Comment 14 Marcel Hasler 2024-10-26 09:38:43 UTC
Created attachment 175249 [details]
Test case 1
Comment 15 Marcel Hasler 2024-10-26 09:38:54 UTC
Created attachment 175250 [details]
Test case 2
Comment 16 Marcel Hasler 2024-10-26 09:39:05 UTC
Created attachment 175251 [details]
Test case 3
Comment 17 Marcel Hasler 2024-10-26 09:41:46 UTC
I can see you are hard to convince, but I'll give it one more shot.

Please have a look at the test cases I attached. Read them, understand them, compile them and run them. If that doesn't convince you, then I guess nothing will. But know that even though your code may happen to "work" in this case, that's by chance, not by design. If you continue doing stuff like that, your luck *will* run out eventually.
Comment 18 Fabio 2024-10-26 15:24:15 UTC
Looking at the third test case I finally got your point.
Thank you for your patience 

Fixed in https://invent.kde.org/network/krdc/-/merge_requests/116/diffs
Comment 19 Marcel Hasler 2024-10-26 17:10:03 UTC
(In reply to Fabio from comment #18)
> Looking at the third test case I finally got your point.
> Thank you for your patience 
> 
> Fixed in https://invent.kde.org/network/krdc/-/merge_requests/116/diffs

Thanks! Just glad I managed to get my point across eventually :-)
Comment 20 Marcel Hasler 2024-10-27 09:32:06 UTC
(In reply to Marcel Hasler from comment #17)
> I can see you are hard to convince, but I'll give it one more shot.
> 
> Please have a look at the test cases I attached. Read them, understand them,
> compile them and run them. If that doesn't convince you, then I guess
> nothing will. But know that even though your code may happen to "work" in
> this case, that's by chance, not by design. If you continue doing stuff like
> that, your luck *will* run out eventually.

I guess that warning came too late, the same thing is in fact already being done in other places. As a test I printed the proxy and gateway settings with qDebug() just before the call to freerdp_connect(). As expected the output is complete garbage and after the message that it cannot connect, krdc crashes.

According to the sources it looks like freerdp actually takes ownership of all settings strings and frees them on exit, so using strdup (or _strdup from <winpr/string.h> or std::strdup once available) would be the correct solution here.

qstrdup is already used for ServerHostname, Username and Password. I would, however, recommend using C strdup, because qstrdup uses new[] to allocate the string which should be deallocated with delete[] accordingly, whereas freerdp naturally uses free() instead. So using qstrdup to copy the strings would once again be UB.
Comment 21 Fabio 2024-10-28 10:10:39 UTC
(In reply to Marcel Hasler from comment #20)
> I guess that warning came too late, the same thing is in fact already being
> done in other places. As a test I printed the proxy and gateway settings
> with qDebug() just before the call to freerdp_connect(). As expected the
> output is complete garbage and after the message that it cannot connect,
> krdc crashes.
> 
> According to the sources it looks like freerdp actually takes ownership of
> all settings strings and frees them on exit, so using strdup (or _strdup
> from <winpr/string.h> or std::strdup once available) would be the correct
> solution here.

Thank you for checking the rest of the code.
A MR is already queued that adds support for freerdp3 and also changes the way all the settings are set, using freerdp_settings_set_string() that internally calls update_string_copy_(), so that freerdp creates its own copy of the char* when they still exists.
I guess that should fix the other issues you found, see eg.:
https://invent.kde.org/network/krdc/-/merge_requests/113/diffs?commit_id=e1c9965f52d0aa7e2e0d9d02f9b4417914ea8748#22035f78003a0693934d972e85db265b9deaa2f4_474_574


> 
> qstrdup is already used for ServerHostname, Username and Password. I would,
> however, recommend using C strdup, because qstrdup uses new[] to allocate
> the string which should be deallocated with delete[] accordingly, whereas
> freerdp naturally uses free() instead. So using qstrdup to copy the strings
> would once again be UB.

Thanks, will integrate this in the MR