bpo-31512: Add non-elevated symlink support for Windows (GH-3652)

This commit is contained in:
Vidar Tonaas Fauske 2019-04-09 20:19:46 +02:00 committed by Steve Dower
parent 8709490f48
commit 0e10766574
4 changed files with 53 additions and 79 deletions

View File

@ -2699,19 +2699,15 @@ features:
as a directory if *target_is_directory* is ``True`` or a file symlink (the as a directory if *target_is_directory* is ``True`` or a file symlink (the
default) otherwise. On non-Windows platforms, *target_is_directory* is ignored. default) otherwise. On non-Windows platforms, *target_is_directory* is ignored.
Symbolic link support was introduced in Windows 6.0 (Vista). :func:`symlink`
will raise a :exc:`NotImplementedError` on Windows versions earlier than 6.0.
This function can support :ref:`paths relative to directory descriptors This function can support :ref:`paths relative to directory descriptors
<dir_fd>`. <dir_fd>`.
.. note:: .. note::
On Windows, the *SeCreateSymbolicLinkPrivilege* is required in order to On newer versions of Windows 10, unprivileged accounts can create symlinks
successfully create symlinks. This privilege is not typically granted to if Developer Mode is enabled. When Developer Mode is not available/enabled,
regular users but is available to accounts which can escalate privileges the *SeCreateSymbolicLinkPrivilege* privilege is required, or the process
to the administrator level. Either obtaining the privilege or running your must be run as an administrator.
application as an administrator are ways to successfully create symlinks.
:exc:`OSError` is raised when the function is called by an unprivileged :exc:`OSError` is raised when the function is called by an unprivileged
@ -2729,6 +2725,9 @@ features:
.. versionchanged:: 3.6 .. versionchanged:: 3.6
Accepts a :term:`path-like object` for *src* and *dst*. Accepts a :term:`path-like object` for *src* and *dst*.
.. versionchanged:: 3.8
Added support for unelevated symlinks on Windows with Developer Mode.
.. function:: sync() .. function:: sync()

View File

@ -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.

View File

@ -284,10 +284,7 @@ extern char *ctermid_r(char *);
#include <windows.h> #include <windows.h>
#include <shellapi.h> /* for ShellExecute() */ #include <shellapi.h> /* for ShellExecute() */
#include <lmcons.h> /* for UNLEN */ #include <lmcons.h> /* for UNLEN */
#ifdef SE_CREATE_SYMBOLIC_LINK_NAME /* Available starting with Vista */
#define HAVE_SYMLINK #define HAVE_SYMLINK
static int win32_can_symlink = 0;
#endif
#endif /* _MSC_VER */ #endif /* _MSC_VER */
#ifndef MAXPATHLEN #ifndef MAXPATHLEN
@ -7755,26 +7752,6 @@ os_readlink_impl(PyObject *module, path_t *path, int dir_fd)
#if defined(MS_WINDOWS) #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 */ /* Remove the last portion of the path - return 0 on success */
static int static int
_dirnameW(WCHAR *path) _dirnameW(WCHAR *path)
@ -7878,33 +7855,57 @@ os_symlink_impl(PyObject *module, path_t *src, path_t *dst,
{ {
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
DWORD result; 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 #else
int result; int result;
#endif #endif
#ifdef MS_WINDOWS #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_ALLOW_THREADS
_Py_BEGIN_SUPPRESS_IPH _Py_BEGIN_SUPPRESS_IPH
/* if src is a directory, ensure target_is_directory==1 */ /* if src is a directory, ensure flags==1 (target_is_directory bit) */
target_is_directory |= _check_dirW(src->wide, dst->wide); if (target_is_directory || _check_dirW(src->wide, dst->wide)) {
result = Py_CreateSymbolicLinkW(dst->wide, src->wide, flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
target_is_directory); }
result = CreateSymbolicLinkW(dst->wide, src->wide, flags);
_Py_END_SUPPRESS_IPH _Py_END_SUPPRESS_IPH
Py_END_ALLOW_THREADS 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;
}
}
if (!result) if (!result)
return path_error2(src, dst); return path_error2(src, dst);
@ -13469,35 +13470,6 @@ static PyMethodDef posix_methods[] = {
{NULL, NULL} /* Sentinel */ {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 static int
all_ins(PyObject *m) all_ins(PyObject *m)
{ {
@ -14105,10 +14077,6 @@ INITFUNC(void)
PyObject *list; PyObject *list;
const char * const *trace; const char * const *trace;
#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS)
win32_can_symlink = enable_symlink();
#endif
m = PyModule_Create(&posixmodule); m = PyModule_Create(&posixmodule);
if (m == NULL) if (m == NULL)
return NULL; return NULL;

View File

@ -45,6 +45,11 @@ typedef struct {
FIELD_OFFSET(_Py_REPARSE_DATA_BUFFER, GenericReparseBuffer) FIELD_OFFSET(_Py_REPARSE_DATA_BUFFER, GenericReparseBuffer)
#define _Py_MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 ) #define _Py_MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 )
// Defined in WinBase.h in 'recent' versions of Windows 10 SDK
#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
#define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2
#endif
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif