dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.05k stars 1.17k forks source link

Full temporary folder will crash cursor initialization #696

Open lindexi opened 5 years ago

lindexi commented 5 years ago

The cursor will crash at initialization when the temporary folder full.

Actual behavior:

I set a cursor from a resource and use this code.

var uri = new Uri("pack://application:,,,/Foo.cur");
var resource = Application.GetResourceStream(uri);
Cursor = new Cursor(resource.Stream); 

I can find the cursor will crash at initialization when the temporary folder full.

System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
System.IO.Directory.InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj, Boolean checkHost)
System.IO.Directory.InternalCreateDirectoryHelper(String path, Boolean checkHost)
System.IO.Directory.CreateDirectory(String path)
System.IO.FileHelper.CreateAndOpenTemporaryFile(String& filePath, FileAccess fileAccess, FileOptions fileOptions, String extension, String subFolder)
System.Windows.Input.Cursor.LoadFromStream(Stream cursorStream)
System.Windows.Input.Cursor..ctor(Stream cursorStream, Boolean scaleWithDpi)
System.Windows.Input.Cursor..ctor(Stream cursorStream)
FawlalnejajerelaWhallgemcurkear.MainWindow..ctor()

Expected behavior:

The cursor can be set.

Minimal repro:

Run this code when the temporary folder full.

        var uri = new Uri("pack://application:,,,/Text.cur");
        var resource = Application.GetResourceStream(uri);
        Cursor = new Cursor(resource.Stream);

And the other way is set a can not visit folder as the temp folder. And you can see the code in https://github.com/lindexi/lindexi_gd/tree/8e346e750fe2075e1366a2a098d63af3b50db177/FawlalnejajerelaWhallgemcurkear that I set a folder name as D:\lindexi\不存在文件 that is an unauthorized access folder.

Reason

The Cursor.LegacyLoadFromStream will generate a temporary file based on the memory stream. But the Path.GetTempFileName Method limit the number of the files is 65535 and it will throw IOException when overcount.

The LoadFromStream code in https://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/Input/Cursor.cs,090cb505b6310a4e

The LegacyLoadFromStream code in https://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/Input/Cursor.cs,935d59bd1efe76e4,references

Actual LoadFromStream may ignore LegacyLoadFromStream. But FileHelper.CreateAndOpenTemporaryFile will generate the file in WPF folder by default and use Path.GetRandomFileName to create a file without check access. And it also crashes when the temp folder can not write.

solution

  1. Set the Environment Variable to the own folder that can read or write.
            Environment.SetEnvironmentVariable("TEMP", newTempFolder);
            Environment.SetEnvironmentVariable("TMP", newTempFolder);

    Why set the environment variable is safe? Because all the process can call the Path.GetTempFileName that will throw IOException when the other process uses it to create and do not delete.

  2. Use the public Cursor(string cursorFile) replace public Cursor(Stream cursorStream) that the first will call LoadFromFile and the LoadFromFile will load the file and do not need to copy to temp folder.

Reference

WPF 光标初始化的时候 temp 文件夹满了无法创建

walterlv commented 5 years ago

Another crash case: If the Windows disk is full, the cursor creation will also crash.

If we don't cache the generated Cursor instances, the temp folder will soon be full and cause the exception above.

lindexi commented 5 years ago

Question: what is Switch.System.Windows.AllowExternalProcessToBlockAccessToTemporaryFiles

lindexi commented 5 years ago

T737514 - An application crashes with UnauthorizedAccessException if a cursor cannot be created

lindexi commented 5 years ago

Avoid calling Path.GetTempFileName · Issue #259 · dotnet/wpf

lindexi commented 5 years ago

Consider providing an API like GetRandomFileName that also creates an empty file · Issue #22972 · dotnet/corefx

lindexi commented 4 years ago

How to solve it? The evil way is change the temp path.

        Environment.SetEnvironmentVariable("TEMP", tempFolder);
        Environment.SetEnvironmentVariable("TMP", tempFolder);

The tempFolder is your custom folder full path. Yes, I fire a new comment just want to tell you should use the full path not a relative path.