bpo-31512: Add non-elevated symlink support for Windows (GH-3652) · python/cpython@0e10766
@@ -284,10 +284,7 @@ extern char *ctermid_r(char *);
284284#include <windows.h>
285285#include <shellapi.h> /* for ShellExecute() */
286286#include <lmcons.h> /* for UNLEN */
287-#ifdef SE_CREATE_SYMBOLIC_LINK_NAME /* Available starting with Vista */
288287#define HAVE_SYMLINK
289-static int win32_can_symlink = 0;
290-#endif
291288#endif /* _MSC_VER */
292289293290#ifndef MAXPATHLEN
@@ -7755,26 +7752,6 @@ os_readlink_impl(PyObject *module, path_t *path, int dir_fd)
7755775277567753#if defined(MS_WINDOWS)
775777547758-/* Grab CreateSymbolicLinkW dynamically from kernel32 */
7759-static BOOLEAN (CALLBACK *Py_CreateSymbolicLinkW)(LPCWSTR, LPCWSTR, DWORD) = NULL;
7760-7761-static int
7762-check_CreateSymbolicLink(void)
7763-{
7764-HINSTANCE hKernel32;
7765-/* only recheck */
7766-if (Py_CreateSymbolicLinkW)
7767-return 1;
7768-7769-Py_BEGIN_ALLOW_THREADS
7770-hKernel32 = GetModuleHandleW(L"KERNEL32");
7771-*(FARPROC*)&Py_CreateSymbolicLinkW = GetProcAddress(hKernel32,
7772-"CreateSymbolicLinkW");
7773-Py_END_ALLOW_THREADS
7774-7775-return Py_CreateSymbolicLinkW != NULL;
7776-}
7777-77787755/* Remove the last portion of the path - return 0 on success */
77797756static int
77807757_dirnameW(WCHAR *path)
@@ -7878,33 +7855,57 @@ os_symlink_impl(PyObject *module, path_t *src, path_t *dst,
78787855{
78797856#ifdef MS_WINDOWS
78807857DWORD result;
7858+DWORD flags = 0;
7859+7860+/* Assumed true, set to false if detected to not be available. */
7861+static int windows_has_symlink_unprivileged_flag = TRUE;
78817862#else
78827863int result;
78837864#endif
7884786578857866#ifdef MS_WINDOWS
7886-if (!check_CreateSymbolicLink()) {
7887-PyErr_SetString(PyExc_NotImplementedError,
7888-"CreateSymbolicLink functions not found");
7889-return NULL;
7890- }
7891-if (!win32_can_symlink) {
7892-PyErr_SetString(PyExc_OSError, "symbolic link privilege not held");
7893-return NULL;
7894- }
7895-#endif
789678677897-#ifdef MS_WINDOWS
7868+if (windows_has_symlink_unprivileged_flag) {
7869+/* Allow non-admin symlinks if system allows it. */
7870+flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
7871+ }
7898787278997873Py_BEGIN_ALLOW_THREADS
79007874_Py_BEGIN_SUPPRESS_IPH
7901-/* if src is a directory, ensure target_is_directory==1 */
7902-target_is_directory |= _check_dirW(src->wide, dst->wide);
7903-result = Py_CreateSymbolicLinkW(dst->wide, src->wide,
7904-target_is_directory);
7875+/* if src is a directory, ensure flags==1 (target_is_directory bit) */
7876+if (target_is_directory || _check_dirW(src->wide, dst->wide)) {
7877+flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
7878+ }
7879+7880+result = CreateSymbolicLinkW(dst->wide, src->wide, flags);
79057881_Py_END_SUPPRESS_IPH
79067882Py_END_ALLOW_THREADS
790778837884+if (windows_has_symlink_unprivileged_flag && !result &&
7885+ERROR_INVALID_PARAMETER == GetLastError()) {
7886+7887+Py_BEGIN_ALLOW_THREADS
7888+_Py_BEGIN_SUPPRESS_IPH
7889+/* This error might be caused by
7890+ SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE not being supported.
7891+ Try again, and update windows_has_symlink_unprivileged_flag if we
7892+ are successful this time.
7893+7894+ NOTE: There is a risk of a race condition here if there are other
7895+ conditions than the flag causing ERROR_INVALID_PARAMETER, and
7896+ another process (or thread) changes that condition in between our
7897+ calls to CreateSymbolicLink.
7898+ */
7899+flags &= ~(SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
7900+result = CreateSymbolicLinkW(dst->wide, src->wide, flags);
7901+_Py_END_SUPPRESS_IPH
7902+Py_END_ALLOW_THREADS
7903+7904+if (result || ERROR_INVALID_PARAMETER != GetLastError()) {
7905+windows_has_symlink_unprivileged_flag = FALSE;
7906+ }
7907+ }
7908+79087909if (!result)
79097910return path_error2(src, dst);
79107911@@ -13469,35 +13470,6 @@ static PyMethodDef posix_methods[] = {
1346913470 {NULL, NULL} /* Sentinel */
1347013471};
134711347213472-13473-#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS)
13474-static int
13475-enable_symlink()
13476-{
13477-HANDLE tok;
13478-TOKEN_PRIVILEGES tok_priv;
13479-LUID luid;
13480-13481-if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &tok))
13482-return 0;
13483-13484-if (!LookupPrivilegeValue(NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &luid))
13485-return 0;
13486-13487-tok_priv.PrivilegeCount = 1;
13488-tok_priv.Privileges[0].Luid = luid;
13489-tok_priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
13490-13491-if (!AdjustTokenPrivileges(tok, FALSE, &tok_priv,
13492-sizeof(TOKEN_PRIVILEGES),
13493- (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL))
13494-return 0;
13495-13496-/* ERROR_NOT_ALL_ASSIGNED returned when the privilege can't be assigned. */
13497-return GetLastError() == ERROR_NOT_ALL_ASSIGNED ? 0 : 1;
13498-}
13499-#endif /* defined(HAVE_SYMLINK) && defined(MS_WINDOWS) */
13500-1350113473static int
1350213474all_ins(PyObject *m)
1350313475{
@@ -14105,10 +14077,6 @@ INITFUNC(void)
1410514077PyObject *list;
1410614078const char * const *trace;
141071407914108-#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS)
14109-win32_can_symlink = enable_symlink();
14110-#endif
14111-1411214080m = PyModule_Create(&posixmodule);
1411314081if (m == NULL)
1411414082return NULL;