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 */

77797756

static 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

78807857

DWORD 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

78827863

int 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+

}

7898787278997873

Py_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

79067882

Py_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+79087909

if (!result)

79097910

return 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-1350113473

static int

1350213474

all_ins(PyObject *m)

1350313475

{

@@ -14105,10 +14077,6 @@ INITFUNC(void)

1410514077

PyObject *list;

1410614078

const char * const *trace;

141071407914108-

#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS)

14109-

win32_can_symlink = enable_symlink();

14110-

#endif

14111-1411214080

m = PyModule_Create(&posixmodule);

1411314081

if (m == NULL)

1411414082

return NULL;