bpo-31512: Add non-elevated symlink support for Windows (GH-3652)
This commit is contained in:
parent
8709490f48
commit
0e10766574
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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.
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue