summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVidar Tonaas Fauske <vidartf@gmail.com>2019-04-09 18:19:46 (GMT)
committerSteve Dower <steve.dower@microsoft.com>2019-04-09 18:19:46 (GMT)
commit0e10766574f4e287cd6b5e5860a1ca75488f4119 (patch)
tree6f501fe8daeaa226bf61905cbeebfd8dfad4f149
parent8709490f48fc27b3dd1a16acb33bea2299c6a575 (diff)
downloadcpython-0e10766574f4e287cd6b5e5860a1ca75488f4119.zip
cpython-0e10766574f4e287cd6b5e5860a1ca75488f4119.tar.gz
cpython-0e10766574f4e287cd6b5e5860a1ca75488f4119.tar.bz2
bpo-31512: Add non-elevated symlink support for Windows (GH-3652)
-rw-r--r--Doc/library/os.rst15
-rw-r--r--Misc/NEWS.d/next/Windows/2017-10-04-12-40-45.bpo-31512.YQeBt2.rst2
-rw-r--r--Modules/posixmodule.c110
-rw-r--r--Modules/winreparse.h5
4 files changed, 53 insertions, 79 deletions
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index 85e240a..f3b5d96 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -2699,19 +2699,15 @@ features:
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.
- 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
<dir_fd>`.
.. note::
- On Windows, the *SeCreateSymbolicLinkPrivilege* is required in order to
- successfully create symlinks. This privilege is not typically granted to
- regular users but is available to accounts which can escalate privileges
- to the administrator level. Either obtaining the privilege or running your
- application as an administrator are ways to successfully create symlinks.
+ On newer versions of Windows 10, unprivileged accounts can create symlinks
+ if Developer Mode is enabled. When Developer Mode is not available/enabled,
+ the *SeCreateSymbolicLinkPrivilege* privilege is required, or the process
+ must be run as an administrator.
:exc:`OSError` is raised when the function is called by an unprivileged
@@ -2729,6 +2725,9 @@ features:
.. versionchanged:: 3.6
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()
diff --git a/Misc/NEWS.d/next/Windows/2017-10-04-12-40-45.bpo-31512.YQeBt2.rst b/Misc/NEWS.d/next/Windows/2017-10-04-12-40-45.bpo-31512.YQeBt2.rst
new file mode 100644
index 0000000..a6dbb5c
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2017-10-04-12-40-45.bpo-31512.YQeBt2.rst
@@ -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.
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 7c4e5f0..e8dbdcc 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -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
- /* 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;
+ }
+ }
+
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;
diff --git a/Modules/winreparse.h b/Modules/winreparse.h
index 28049c9..f06f701 100644
--- a/Modules/winreparse.h
+++ b/Modules/winreparse.h
@@ -45,6 +45,11 @@ typedef struct {
FIELD_OFFSET(_Py_REPARSE_DATA_BUFFER, GenericReparseBuffer)
#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
}
#endif