-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
bpo-31512: Add non-elevated symlink support for dev mode Windows 10 #3652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c34815d
60dc705
021aa77
c9c8667
19b9a08
6c92f67
bf973bd
5132324
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
With the Windows 10 Creators Update, non-elevated users can now create | ||
symlinks as long as the computer has Developer Mode enabled. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -284,10 +284,7 @@ extern char *ctermid_r(char *); | |
#include <windows.h> | ||
#include <shellapi.h> /* for ShellExecute() */ | ||
#include <lmcons.h> /* for UNLEN */ | ||
#ifdef SE_CREATE_SYMBOLIC_LINK_NAME /* Available starting with Vista */ | ||
#define HAVE_SYMLINK | ||
static int win32_can_symlink = 0; | ||
#endif | ||
#endif /* _MSC_VER */ | ||
|
||
#ifndef MAXPATHLEN | ||
|
@@ -7755,26 +7752,6 @@ os_readlink_impl(PyObject *module, path_t *path, int dir_fd) | |
|
||
#if defined(MS_WINDOWS) | ||
|
||
/* Grab CreateSymbolicLinkW dynamically from kernel32 */ | ||
static BOOLEAN (CALLBACK *Py_CreateSymbolicLinkW)(LPCWSTR, LPCWSTR, DWORD) = NULL; | ||
|
||
static int | ||
check_CreateSymbolicLink(void) | ||
{ | ||
HINSTANCE hKernel32; | ||
/* only recheck */ | ||
if (Py_CreateSymbolicLinkW) | ||
return 1; | ||
|
||
Py_BEGIN_ALLOW_THREADS | ||
hKernel32 = GetModuleHandleW(L"KERNEL32"); | ||
*(FARPROC*)&Py_CreateSymbolicLinkW = GetProcAddress(hKernel32, | ||
"CreateSymbolicLinkW"); | ||
Py_END_ALLOW_THREADS | ||
|
||
return Py_CreateSymbolicLinkW != NULL; | ||
} | ||
|
||
/* Remove the last portion of the path - return 0 on success */ | ||
static int | ||
_dirnameW(WCHAR *path) | ||
|
@@ -7878,33 +7855,57 @@ os_symlink_impl(PyObject *module, path_t *src, path_t *dst, | |
{ | ||
#ifdef MS_WINDOWS | ||
DWORD result; | ||
DWORD flags = 0; | ||
|
||
/* Assumed true, set to false if detected to not be available. */ | ||
static int windows_has_symlink_unprivileged_flag = TRUE; | ||
#else | ||
int result; | ||
#endif | ||
|
||
#ifdef MS_WINDOWS | ||
if (!check_CreateSymbolicLink()) { | ||
PyErr_SetString(PyExc_NotImplementedError, | ||
"CreateSymbolicLink functions not found"); | ||
return NULL; | ||
} | ||
if (!win32_can_symlink) { | ||
PyErr_SetString(PyExc_OSError, "symbolic link privilege not held"); | ||
return NULL; | ||
} | ||
#endif | ||
|
||
#ifdef MS_WINDOWS | ||
if (windows_has_symlink_unprivileged_flag) { | ||
/* Allow non-admin symlinks if system allows it. */ | ||
flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; | ||
} | ||
|
||
Py_BEGIN_ALLOW_THREADS | ||
_Py_BEGIN_SUPPRESS_IPH | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to suppress the CRT invalid-parameter handler in this code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to remove it, but it caused test failures (test_os.Win32SynLinkTests.test_buffer_overflow). See #5989, which introduced these macros here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay. That makes sense. I forgot that |
||
/* if src is a directory, ensure target_is_directory==1 */ | ||
target_is_directory |= _check_dirW(src->wide, dst->wide); | ||
result = Py_CreateSymbolicLinkW(dst->wide, src->wide, | ||
target_is_directory); | ||
/* if src is a directory, ensure flags==1 (target_is_directory bit) */ | ||
if (target_is_directory || _check_dirW(src->wide, dst->wide)) { | ||
flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; | ||
} | ||
|
||
result = CreateSymbolicLinkW(dst->wide, src->wide, flags); | ||
_Py_END_SUPPRESS_IPH | ||
Py_END_ALLOW_THREADS | ||
|
||
if (windows_has_symlink_unprivileged_flag && !result && | ||
ERROR_INVALID_PARAMETER == GetLastError()) { | ||
|
||
Py_BEGIN_ALLOW_THREADS | ||
_Py_BEGIN_SUPPRESS_IPH | ||
/* This error might be caused by | ||
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE not being supported. | ||
Try again, and update windows_has_symlink_unprivileged_flag if we | ||
are successful this time. | ||
|
||
NOTE: There is a risk of a race condition here if there are other | ||
conditions than the flag causing ERROR_INVALID_PARAMETER, and | ||
another process (or thread) changes that condition in between our | ||
calls to CreateSymbolicLink. | ||
*/ | ||
flags &= ~(SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE); | ||
result = CreateSymbolicLinkW(dst->wide, src->wide, flags); | ||
_Py_END_SUPPRESS_IPH | ||
Py_END_ALLOW_THREADS | ||
|
||
if (result || ERROR_INVALID_PARAMETER != GetLastError()) { | ||
windows_has_symlink_unprivileged_flag = FALSE; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A note: on systems without the new flag support, we'll do both calls on each |
||
} | ||
|
||
if (!result) | ||
return path_error2(src, dst); | ||
|
||
|
@@ -13469,35 +13470,6 @@ static PyMethodDef posix_methods[] = { | |
{NULL, NULL} /* Sentinel */ | ||
}; | ||
|
||
|
||
#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS) | ||
static int | ||
enable_symlink() | ||
{ | ||
HANDLE tok; | ||
TOKEN_PRIVILEGES tok_priv; | ||
LUID luid; | ||
|
||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &tok)) | ||
return 0; | ||
|
||
if (!LookupPrivilegeValue(NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &luid)) | ||
return 0; | ||
|
||
tok_priv.PrivilegeCount = 1; | ||
tok_priv.Privileges[0].Luid = luid; | ||
tok_priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | ||
|
||
if (!AdjustTokenPrivileges(tok, FALSE, &tok_priv, | ||
sizeof(TOKEN_PRIVILEGES), | ||
(PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL)) | ||
return 0; | ||
|
||
/* ERROR_NOT_ALL_ASSIGNED returned when the privilege can't be assigned. */ | ||
return GetLastError() == ERROR_NOT_ALL_ASSIGNED ? 0 : 1; | ||
} | ||
#endif /* defined(HAVE_SYMLINK) && defined(MS_WINDOWS) */ | ||
|
||
static int | ||
all_ins(PyObject *m) | ||
{ | ||
|
@@ -14105,10 +14077,6 @@ INITFUNC(void) | |
PyObject *list; | ||
const char * const *trace; | ||
|
||
#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS) | ||
win32_can_symlink = enable_symlink(); | ||
#endif | ||
|
||
m = PyModule_Create(&posixmodule); | ||
if (m == NULL) | ||
return NULL; | ||
|
Uh oh!
There was an error while loading. Please reload this page.