Boost C++ Libraries: Ticket #10900: read_symlink fails to correctly read NTFS junctions https://svn.boost.org/trac10/ticket/10900 <p> <em>Tested on Windows 7 64-bit.</em> </p> <p> NTFS directory junctions are now recognized as a symlink by <code>is_reparse_point_a_symlink</code> but <code>read_symlink</code> does not correctly handle those "mount point" reparse points yet. </p> <p> Among other things this also breaks the <code>canonical</code> operation. </p> <p> The <code>REPARSE_DATA_BUFFER</code> returned by <code>DeviceIoControl</code> needs to be accessed differently for regular symlinks and mount points. See msdn.microsoft.com/en-us/library/ff552012.aspx </p> <p> Accessing the "PrintName" as a symlink typically results in the first two (wide) characters of the path being skipped and generally in undefined behavior. </p> <p> A possible fix would be to add a conditional statement checking <code>info.rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT</code> and then amend the <code>symlink_path.assign</code> call accordingly (using <code>info.rdb.MountPointReparseBuffer</code> instead of <code>info.rdb.SymbolicLinkReparseBuffer</code>). </p> <p> In version 1.57.0 the offending code can be found in <em>libs/filesystem/src/operations.cpp</em> around line 1514. </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/10900 Trac 1.4.3 anonymous Wed, 07 Jan 2015 21:03:25 GMT <link>https://svn.boost.org/trac10/ticket/10900#comment:1 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10900#comment:1</guid> <description> <p> There are multiple types of reparse points other than symlinks and junctions - the types supported (IO_REPARSE_TAG_MOUNT_POINT and IO_REPARSE_TAG_SYMLINK) must both be explicitly detected in order to reject anything else. </p> <p> Other issues: </p> <ol><li>read_symlink extracts the reparse point's <a class="missing wiki">PrintName</a>, which isn't guaranteed to be valid - according to <a class="ext-link" href="http://msdn.microsoft.com/en-us/library/cc232006.aspx"><span class="icon">​</span>http://msdn.microsoft.com/en-us/library/cc232006.aspx</a>, "the print name SHOULD be an informative pathname, suitable for display to a user, that also identifies the target of the symbolic link", but it's not guaranteed to be the actual target (most notably, with junctions created using the Sysinternals junction.exe tool, the <a class="missing wiki">PrintName</a> is completely empty). It should probably be extracting the <a class="missing wiki">SubstituteName</a> instead, though that would likely require stripping off the leading "\??\" to yield a properly usable pathname (unless it's a device ID, in which case all bets are off). </li><li>If Junction support is added, it should not be restricted to Windows Vista and later, since directory junctions were introduced back in Windows 2000. </li></ol> </description> <category>Ticket</category> </item> <item> <dc:creator>Beman Dawes</dc:creator> <pubDate>Sat, 10 Jan 2015 17:16:29 GMT</pubDate> <title>status changed https://svn.boost.org/trac10/ticket/10900#comment:2 https://svn.boost.org/trac10/ticket/10900#comment:2 <ul> <li><strong>status</strong> <span class="trac-field-old">new</span> → <span class="trac-field-new">assigned</span> </li> </ul> <p> This is a useful request, but the priority is low relative to other filesystem issues. </p> <p> If you would like to help, the first step is to create a test program. </p> <p> See <a class="ext-link" href="http://boostorg.github.io/filesystem/issue_reporting.html"><span class="icon">​</span>http://boostorg.github.io/filesystem/issue_reporting.html</a> </p> <p> The test program first needs to create a test directory structure, and then apply the various operational functions like read_symlink() that you would like to see work, and also test cases that should fail. </p> <p> Although it would be nice if the filesystem library itself could create the directory structure, to get started it would be OK if the Windows API was used. </p> <p> If you (or anyone else) could help with creating test cases, I'd be willing to raise the priority of making the actual changes to filesystem operational functions that are needed. </p> <p> Thanks for your interest in Boost.Filesystem. </p> <p> --Beman </p> Ticket anonymous Sat, 30 May 2015 14:59:26 GMT <link>https://svn.boost.org/trac10/ticket/10900#comment:3 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10900#comment:3</guid> <description> <p> test case is already on file libs/filesystem/src/operations.cpp around line 139. But I'll provide another (for a system with Windows 7 or above, volumes C: and D:) Start by creating a directory symlink [Users] in volume D: pointing to [C:\Users]: </p> <p> D:\&gt;mklink /D Users C:\Users </p> <p> Create a simple program to resolve path dot: </p> <p> <em>app.cpp ... cout &lt;&lt; boost::filesystem::canonical(".") &lt;&lt; '\n\n'; ... </em></p> <p> Compile and run the program from the symlink </p> <p> D:\Users\Public\Documents&gt;app.exe </p> <p> The code will burn CPU trapped in the loop at line 813 of operations.cpp, trying to resolve the symlink (internal temp buffer "C:/\\Users") </p> <p> At line 1508, from where REPARSE_DATA_BUFFER is used, <a class="missing wiki">PrintNameOffset</a> and <a class="missing wiki">PrintNameLength</a> values are the same to both sub struct <a class="missing wiki">SymbolicLinkReparseBuffer</a> and <a class="missing wiki">MountPointReparseBuffer</a>. </p> <p> The values fails when applied to <a class="missing wiki">SymbolicLinkReparseBuffer</a>.<a class="missing wiki">PathBuffer</a>. But works right on <a class="missing wiki">MountPointReparseBuffer</a>.<a class="missing wiki">PathBuffer</a>. </p> <p> With this (example only) change: </p> <pre class="wiki"> if (!error(::DeviceIoControl(h.handle, FSCTL_GET_REPARSE_POINT, 0, 0, info.buf, sizeof(info), &amp;sz, 0) == 0 ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::read_symlink" )) symlink_path.assign( + static_cast&lt;wchar_t*&gt;(info.rdb.MountPointReparseBuffer.PathBuffer) + + info.rdb.MountPointReparseBuffer.PrintNameOffset / sizeof(wchar_t), + static_cast&lt;wchar_t*&gt;(info.rdb.MountPointReparseBuffer.PathBuffer) + + info.rdb.MountPointReparseBuffer.PrintNameOffset / sizeof(wchar_t) + + info.rdb.MountPointReparseBuffer.PrintNameLength / sizeof(wchar_t)); - static_cast&lt;wchar_t*&gt;(info.rdb.SymbolicLinkReparseBuffer.PathBuffer) - + info.rdb.SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(wchar_t), - static_cast&lt;wchar_t*&gt;(info.rdb.SymbolicLinkReparseBuffer.PathBuffer) - + info.rdb.SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(wchar_t) - + info.rdb.SymbolicLinkReparseBuffer.PrintNameLength/sizeof(wchar_t)); </pre><p> Output is correct: "C:/Users\Public\Documents" </p> </description> <category>Ticket</category> </item> <item> <dc:creator>anonymous</dc:creator> <pubDate>Wed, 10 Jun 2015 12:07:39 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10900#comment:4 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10900#comment:4</guid> <description> <p> Correction: on the example above, on making of the symlink, the correct option is /J for junction </p> <p> D:\&gt;mklink /J Users C:\Users </p> </description> <category>Ticket</category> </item> <item> <dc:creator>anonymous</dc:creator> <pubDate>Wed, 03 Aug 2016 14:44:35 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10900#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10900#comment:5</guid> <description> <p> This is important, it breaks certain programs (eg. Roblox) which try and check directories that might be junctioned to another folder or mount point. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Biohazard</dc:creator> <pubDate>Wed, 03 May 2017 11:23:26 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10900#comment:6 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10900#comment:6</guid> <description> <p> I had a lot of grief because of this, Boost does not handle symlinks and mount points on Windows properly (Testing Windows 7, 8.1, 10). </p> <p> Just sharing my solution here, maybe it helps someone else. </p> <p> <strong>path read_symlink(const path&amp; p, system::error_code* ec)</strong> must be able to deal with symlinks and mount points. None of the above solutions are resolving the mount point volume name from a device ID if one is returned. </p> <pre class="wiki">if (!error(::DeviceIoControl(h.handle, FSCTL_GET_REPARSE_POINT, 0, 0, info.buf, sizeof(info), &amp;sz, 0) == 0 ? BOOST_ERRNO : 0, p, ec, "boost::filesystem::read_symlink" )) { if (info.rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { symlink_path.assign( static_cast&lt;wchar_t*&gt;(info.rdb.MountPointReparseBuffer.PathBuffer) + info.rdb.MountPointReparseBuffer.PrintNameOffset/sizeof(wchar_t), static_cast&lt;wchar_t*&gt;(info.rdb.MountPointReparseBuffer.PathBuffer) + info.rdb.MountPointReparseBuffer.PrintNameOffset/sizeof(wchar_t) + info.rdb.MountPointReparseBuffer.PrintNameLength/sizeof(wchar_t)); const wchar_t *mountPointVolumeStart = wcsstr(symlink_path.c_str(), L"\\??\\Volume{"); if (mountPointVolumeStart != nullptr) { wchar_t pathNames[MAX_PATH * 4]; DWORD retLen; path volumePath(L"\\\\?\\"); volumePath += (mountPointVolumeStart + 4); if (GetVolumePathNamesForVolumeName(volumePath.c_str(), pathNames, MAX_PATH * 4, &amp;retLen) != FALSE &amp;&amp; retLen &gt; 0) { symlink_path = pathNames; } } } else if (info.rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK) { symlink_path.assign( static_cast&lt;wchar_t*&gt;(info.rdb.SymbolicLinkReparseBuffer.PathBuffer) + info.rdb.SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(wchar_t), static_cast&lt;wchar_t*&gt;(info.rdb.SymbolicLinkReparseBuffer.PathBuffer) + info.rdb.SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(wchar_t) + info.rdb.SymbolicLinkReparseBuffer.PrintNameLength/sizeof(wchar_t)); } } </pre><p> In <strong>path canonical(const path&amp; p, const path&amp; base, system::error_code* ec)</strong> a dead lock must now be fixed, because a drive solely mounted as a directory will cause <strong>canonical()</strong> to loop indefinitely over the same mount point. I fixed it by comparing the link and resolved link and skipping further iteration if they are equal. </p> <pre class="wiki">if (is_sym) { path link(detail::read_symlink(result, ec)); path cmpLink(link); cmpLink.make_preferred().remove_trailing_separator(); path cmpResult(result); cmpResult.make_preferred().remove_trailing_separator(); if (cmpResult != cmpLink) { if (ec &amp;&amp; *ec) return path(); </pre><p> There is also another issue in canonical I had to add a work around for, namely <strong>C:</strong> being treated as a mount point, causing another dead lock. I just excluded lonely drive letters in <strong>symlink_status</strong>. Clearly not the best solution but I don't have time to find something better: </p> <pre class="wiki">const bool isDriveLetter = p.size() == 2 &amp;&amp; p.c_str()[1] == L':'; if (isDriveLetter) { return file_status(type_unknown, make_permissions(p, attr)); } </pre> </description> <category>Ticket</category> </item> <item> <author>martin.apel@…</author> <pubDate>Fri, 22 Sep 2017 08:59:32 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10900#comment:7 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10900#comment:7</guid> <description> <p> There are quite a number of bug reports, which all have to do with how Boost.Filesystem handles reparse points, e.g. <a class="new ticket" href="https://svn.boost.org/trac10/ticket/11873" title="#11873: Bugs: boost::filesystem does not work on windows when multiple processes ... (new)">#11873</a>, <a class="new ticket" href="https://svn.boost.org/trac10/ticket/11057" title="#11057: Bugs: fs::copy fails with FILE_ATTRIBUTE_REPARSE_POINT attribute (new)">#11057</a>, <a class="closed ticket" href="https://svn.boost.org/trac10/ticket/9016" title="#9016: Bugs: filesystem::is_directory() returns false for junction (closed: fixed)">#9016</a>, <a class="new ticket" href="https://svn.boost.org/trac10/ticket/5649" title="#5649: Bugs: create_directories() fails with NTFS mounted volumes (new)">#5649</a>. I have had similar problems as described here, especially in conjunction with feeding the output of canonical into create_directories, which does not work, when some sort of reparse point (drive mounted as directory, deduplicated files) are involved. I therefore have attempted to fix the problem properly and have attached a corresponding patch against 1.65.1. Please review and is possible, merge this into Boost mainline. </p> </description> <category>Ticket</category> </item> <item> <author>martin.apel@…</author> <pubDate>Fri, 22 Sep 2017 09:00:29 GMT</pubDate> <title>attachment set https://svn.boost.org/trac10/ticket/10900 https://svn.boost.org/trac10/ticket/10900 <ul> <li><strong>attachment</strong> → <span class="trac-field-new">boost.patch.165</span> </li> </ul> <p> Patch to improve reparse point handling </p> Ticket