Skip to content
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

Remove false dependency on pywin32 #226

Closed
mattip opened this issue May 9, 2021 · 26 comments · Fixed by #230
Closed

Remove false dependency on pywin32 #226

mattip opened this issue May 9, 2021 · 26 comments · Fixed by #230
Milestone

Comments

@mattip
Copy link

mattip commented May 9, 2021

There is a stray dependency on pywin32 in setup.cfg

pywin32>=1.0 ; sys_platform == 'win32'

Is it still needed?

@mattip
Copy link
Author

mattip commented May 9, 2021

@stonebig: If you delete those lines can you install jupyter-core on PyPy windows?

@stonebig
Copy link

stonebig commented May 9, 2021

trying now.. now pip is looking for alternate send2trash wheels when I ask "jupyter" directly... obscure new pip.
For now I can install till:

  • jupyter-core,
  • ipython,
  • .. needing pywinpty, ... because of terminado, needed per jupyter-server and notebook..

so, apparently, pywinpty is my next blocker to Jupyter on PyPy3 windows, and I'm clueless to compile it
compiling Notebook is also intimidating... jupyter/notebook#6053

@vidartf
Copy link
Contributor

vidartf commented May 13, 2021

Note that the names of the modules that are used are not the same name as the package. pywin32 imports are used e.g. here:

import win32api
import win32security

@mattip
Copy link
Author

mattip commented May 13, 2021

Thanks for the clarification. I guess this is a hard dependency then until

  • pywin32 is broken up into more manageable pieces
  • python + windows gets better built-in support for those security features
  • someone re-writes those functions using ctypes instead.
  • or some other alternative comes along

@mattip mattip closed this as completed May 13, 2021
@stonebig
Copy link

stonebig commented May 13, 2021

If it's the only blocker between Jupyter and PyPy on Windows, a rescue team will come ... re-checking if I can workaround

@stonebig
Copy link

stonebig commented May 13, 2021

failed with this receipe:

  • remove pywin32 dependancy and usage of it in jupyter_core,
  • remove terminado dependancy on pywinpty (too hard to compile)

jupyter notebook launches the welcome page, but kernel crashes when creating a new notebook
winpython/winpython#982 (comment)

@stonebig
Copy link

stonebig commented May 13, 2021

any good idea for next try welcomed (not an absolute failure on "jupyter notebook", but not good)

@stonebig
Copy link

faling to compile pywin32 with PyPy3 too ... a bad luck day.

@cgohlke
Copy link
Contributor

cgohlke commented May 22, 2021

remove terminado dependancy on pywinpty (too hard to compile)

Compiles without issues but terminado might require an older version? https://www.lfd.uci.edu/~gohlke/pythonlibs/#pywinpty

someone re-writes those functions using ctypes instead.

should be easy, no?

I ran into another jupyter_core relate issue:

    Traceback (most recent call last):
      File "X:\pypy3\site-packages\tornado\web.py", line 1704, in _execute
        result = await result
      File "X:\pypy3\site-packages\jupyter_server\services\contents\handlers.py", line 111, in get
        path=path, type=type, format=format, content=content,
      File "X:\pypy3\site-packages\jupyter_server\services\contents\filemanager.py", line 393, in get
        model = self._dir_model(path, content=content)
      File "X:\pypy3\site-packages\jupyter_server\services\contents\filemanager.py", line 293, in _dir_model
        if self.allow_hidden or not is_file_hidden(os_path, stat_res=st):
      File "X:\pypy3\site-packages\jupyter_core\paths.py", line 270, in is_file_hidden_win
        if stat_res.st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN:
    AttributeError: 'stat_result' object has no attribute 'st_file_attributes'

@mattip
Copy link
Author

mattip commented May 22, 2021

It seems PyPy never added that attribute, which was part of python3.5 for windows. I opened a PyPy issue.

@stonebig
Copy link

Awesome Christoph !
image

@stonebig
Copy link

I suppose "easy" for Christoph means "doable" for Steve Dower, but "hard" is a better word for me.

Baby step1

import ctypes
 
def get_display_name():
    """ does win32api.GetUserNameEx(win32api.NameSamCompatible)"""
    GetUserNameEx = ctypes.windll.secur32.GetUserNameExW
    # from https://sjohannes.wordpress.com/2010/06/19/win32-python-getting-users-display-name-using-ctypes/
    # NameDisplay = 3
    NameDisplay = 2
    size = ctypes.pointer(ctypes.c_ulong(0))
    GetUserNameEx(NameDisplay, None, size)
 
    nameBuffer = ctypes.create_unicode_buffer(size.contents.value)
    GetUserNameEx(NameDisplay, nameBuffer, size)
    return nameBuffer.value

@stonebig
Copy link

stonebig commented May 24, 2021

for "LookupAccountName", I found someone who may have done it: https://github.com/MarioVilas/winappdbg

for "win32security.CreateWellKnownSid(win32security.WinBuiltinAdministratorsSid)", I found something there:
https://bugs.python.org/file41439/integrity_level.py

threre are pieces in https://github.com/openstack/os-win, but its depedencies end up using pywin32...

@cgohlke
Copy link
Contributor

cgohlke commented May 24, 2021

I suppose "easy" for Christoph means "doable" for Steve Dower, but "hard" is a better word for me.

Sorry, my comment wasn't meant to be serious. Using Win32 API and ctypes is very tedious and you'll end up with hundreds lines of code to replace a few lines of pywin32.

@stonebig
Copy link

well, I was not sure it was "easy" or "not so complex" before trying, so I tried, as otherwise Jupyter will not work on PyPy3

@cgohlke
Copy link
Contributor

cgohlke commented May 24, 2021

otherwise Jupyter will not work on PyPy3

To experiment on PyPy3, you can use an empty win32_restrict_file_to_user function.

@stonebig
Copy link

well, it breaks when I open a ".ipynb" notebook. does it do the same for you ?

image

@cgohlke
Copy link
Contributor

cgohlke commented May 24, 2021

well, it breaks when I open a ".ipynb" notebook. does it do the same for you ?

Probably unrelated. But yes, I get the same error. Others too.

@cgohlke
Copy link
Contributor

cgohlke commented May 24, 2021

someone re-writes those functions using ctypes instead.

Works for me, but not well tested. Maybe someone can try it:

def win32_restrict_file_to_user(fname):
    """Secure a windows file to read-only access for the user.
    Follows guidance from win32 library creator:
    http://timgolden.me.uk/python/win32_how_do_i/add-security-to-a-file.html

    This method should be executed against an already generated file which
    has no secrets written to it yet.

    Parameters
    ----------

    fname : unicode
        The path to the file to secure

    """
    import ctypes
    from ctypes import wintypes

    advapi32 = ctypes.WinDLL('advapi32', use_last_error=True)
    secur32 = ctypes.WinDLL('secur32', use_last_error=True)

    NameSamCompatible = 2
    WinBuiltinAdministratorsSid = 26
    DACL_SECURITY_INFORMATION = 4
    ACL_REVISION = 2
    ERROR_INSUFFICIENT_BUFFER = 122
    ERROR_MORE_DATA = 234

    SYNCHRONIZE = 0x100000
    DELETE = 0x00010000
    STANDARD_RIGHTS_REQUIRED = 0xF0000
    STANDARD_RIGHTS_READ = 0x20000
    STANDARD_RIGHTS_WRITE = 0x20000
    FILE_READ_DATA = 1
    FILE_READ_EA = 8
    FILE_READ_ATTRIBUTES = 128
    FILE_WRITE_DATA = 2
    FILE_APPEND_DATA = 4
    FILE_WRITE_EA = 16
    FILE_WRITE_ATTRIBUTES = 256
    FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF
    FILE_GENERIC_READ = (
        STANDARD_RIGHTS_READ
        | FILE_READ_DATA
        | FILE_READ_ATTRIBUTES
        | FILE_READ_EA
        | SYNCHRONIZE
    )
    FILE_GENERIC_WRITE = (
        STANDARD_RIGHTS_WRITE
        | FILE_WRITE_DATA
        | FILE_WRITE_ATTRIBUTES
        | FILE_WRITE_EA
        | FILE_APPEND_DATA
        | SYNCHRONIZE
    )

    class ACL(ctypes.Structure):
        _fields_ = [
            ('AclRevision', wintypes.BYTE),
            ('Sbz1', wintypes.BYTE),
            ('AclSize', wintypes.WORD),
            ('AceCount', wintypes.WORD),
            ('Sbz2', wintypes.WORD),
        ]

    PSID = ctypes.c_void_p
    PACL = ctypes.POINTER(ACL)
    PSECURITY_DESCRIPTOR = ctypes.POINTER(wintypes.BYTE)

    def _nonzero_success(result, func, args):
        if not result:
            raise ctypes.WinError(ctypes.get_last_error())
        return args

    secur32.GetUserNameExW.errcheck = _nonzero_success
    secur32.GetUserNameExW.restype = wintypes.BOOL
    secur32.GetUserNameExW.argtypes = (
        ctypes.c_int,  # EXTENDED_NAME_FORMAT NameFormat
        wintypes.LPWSTR,  # LPWSTR lpNameBuffer,
        wintypes.PULONG,  # PULONG nSize
    )

    advapi32.CreateWellKnownSid.errcheck = _nonzero_success
    advapi32.CreateWellKnownSid.restype = wintypes.BOOL
    advapi32.CreateWellKnownSid.argtypes = (
        wintypes.DWORD,  # WELL_KNOWN_SID_TYPE WellKnownSidType
        PSID,  # PSID DomainSid
        PSID,  # PSID pSid
        wintypes.PDWORD,  # DWORD *cbSid
    )

    advapi32.LookupAccountNameW.errcheck = _nonzero_success
    advapi32.LookupAccountNameW.restype = wintypes.BOOL
    advapi32.LookupAccountNameW.argtypes = (
        wintypes.LPWSTR,  # LPCWSTR lpSystemName
        wintypes.LPWSTR,  # LPCWSTR lpAccountName
        PSID,  # PSID Sid
        wintypes.LPDWORD,  # LPDWORD cbSid
        wintypes.LPWSTR,  # LPCWSTR ReferencedDomainName
        wintypes.LPDWORD,  # LPDWORD cchReferencedDomainName
        wintypes.LPDWORD,  # PSID_NAME_USE peUse
    )

    advapi32.AddAccessAllowedAce.errcheck = _nonzero_success
    advapi32.AddAccessAllowedAce.restype = wintypes.BOOL
    advapi32.AddAccessAllowedAce.argtypes = (
        PACL,  # PACL pAcl
        wintypes.DWORD,  # DWORD dwAceRevision
        wintypes.DWORD,  # DWORD AccessMask
        PSID,  # PSID pSid
    )

    advapi32.SetSecurityDescriptorDacl.errcheck = _nonzero_success
    advapi32.SetSecurityDescriptorDacl.restype = wintypes.BOOL
    advapi32.SetSecurityDescriptorDacl.argtypes = (
        PSECURITY_DESCRIPTOR,  # PSECURITY_DESCRIPTOR pSecurityDescriptor
        wintypes.BOOL,  # BOOL bDaclPresent
        PACL,  # PACL pDacl
        wintypes.BOOL,  # BOOL bDaclDefaulted
    )

    advapi32.GetFileSecurityW.errcheck = _nonzero_success
    advapi32.GetFileSecurityW.restype = wintypes.BOOL
    advapi32.GetFileSecurityW.argtypes = (
        wintypes.LPCWSTR,  # LPCWSTR lpFileName
        wintypes.DWORD,  # SECURITY_INFORMATION RequestedInformation
        PSECURITY_DESCRIPTOR,  # PSECURITY_DESCRIPTOR pSecurityDescriptor
        wintypes.DWORD,  # DWORD nLength
        wintypes.LPDWORD,  # LPDWORD lpnLengthNeeded
    )

    advapi32.SetFileSecurityW.errcheck = _nonzero_success
    advapi32.SetFileSecurityW.restype = wintypes.BOOL
    advapi32.SetFileSecurityW.argtypes = (
        wintypes.LPCWSTR,  # LPCWSTR lpFileName
        wintypes.DWORD,  # SECURITY_INFORMATION SecurityInformation
        PSECURITY_DESCRIPTOR,  # PSECURITY_DESCRIPTOR pSecurityDescriptor
    )

    advapi32.MakeAbsoluteSD.errcheck = _nonzero_success
    advapi32.MakeAbsoluteSD.restype = wintypes.BOOL
    advapi32.MakeAbsoluteSD.argtypes = (
        PSECURITY_DESCRIPTOR,  # pSelfRelativeSecurityDescriptor
        PSECURITY_DESCRIPTOR,  # pAbsoluteSecurityDescriptor
        wintypes.LPDWORD,  # LPDWORD lpdwAbsoluteSecurityDescriptorSize
        PACL,  # PACL pDacl
        wintypes.LPDWORD,  # LPDWORD lpdwDaclSize
        PACL,  # PACL pSacl
        wintypes.LPDWORD,  # LPDWORD lpdwSaclSize
        PSID,  # PSID pOwner
        wintypes.LPDWORD,  # LPDWORD lpdwOwnerSize
        PSID,  # PSID pPrimaryGroup
        wintypes.LPDWORD,  # LPDWORD lpdwPrimaryGroupSize
    )

    advapi32.MakeSelfRelativeSD.errcheck = _nonzero_success
    advapi32.MakeSelfRelativeSD.restype = wintypes.BOOL
    advapi32.MakeSelfRelativeSD.argtypes = (
        PSECURITY_DESCRIPTOR,  # pAbsoluteSecurityDescriptor
        PSECURITY_DESCRIPTOR,  # pSelfRelativeSecurityDescriptor
        wintypes.LPDWORD,  # LPDWORD lpdwBufferLength
    )

    advapi32.InitializeAcl.errcheck = _nonzero_success
    advapi32.InitializeAcl.restype = wintypes.BOOL
    advapi32.InitializeAcl.argtypes = (
        PACL,  # PACL pAcl,
        wintypes.DWORD,  # DWORD nAclLength,
        wintypes.DWORD,  # DWORD dwAclRevision
    )

    def CreateWellKnownSid(WellKnownSidType):
        # return a SID for predefined aliases
        pSid = (ctypes.c_char * 1)()
        cbSid = wintypes.DWORD()
        try:
            advapi32.CreateWellKnownSid(
                WellKnownSidType, None, pSid, ctypes.byref(cbSid)
            )
        except OSError as e:
            if e.winerror != ERROR_INSUFFICIENT_BUFFER:
                raise
            pSid = (ctypes.c_char * cbSid.value)()
            advapi32.CreateWellKnownSid(
                WellKnownSidType, None, pSid, ctypes.byref(cbSid)
            )
        return pSid[:]

    def GetUserNameEx(NameFormat):
        # return the user or other security principal associated with
        # the calling thread
        nSize = ctypes.pointer(ctypes.c_ulong(0))
        try:
            secur32.GetUserNameExW(NameFormat, None, nSize)
        except WindowsError as e:
            if e.winerror != ERROR_MORE_DATA:
                raise
        if not nSize.contents.value:
            return None
        lpNameBuffer = ctypes.create_unicode_buffer(nSize.contents.value)
        secur32.GetUserNameExW(NameFormat, lpNameBuffer, nSize)
        return lpNameBuffer.value

    def LookupAccountName(lpSystemName, lpAccountName):
        # return a security identifier (SID) for an account on a system
        # and the name of the domain on which the account was found
        cbSid = wintypes.DWORD(0)
        cchReferencedDomainName = wintypes.DWORD(0)
        peUse = wintypes.DWORD(0)
        try:
            advapi32.LookupAccountNameW(
                lpSystemName,
                lpAccountName,
                None,
                ctypes.byref(cbSid),
                None,
                ctypes.byref(cchReferencedDomainName),
                ctypes.byref(peUse),
            )
        except WindowsError as e:
            if e.winerror != ERROR_INSUFFICIENT_BUFFER:
                raise
        Sid = ctypes.create_unicode_buffer('', cbSid.value)
        pSid = ctypes.cast(ctypes.pointer(Sid), wintypes.LPVOID)
        lpReferencedDomainName = ctypes.create_unicode_buffer(
            '', cchReferencedDomainName.value + 1
        )
        success = advapi32.LookupAccountNameW(
            lpSystemName,
            lpAccountName,
            pSid,
            ctypes.byref(cbSid),
            lpReferencedDomainName,
            ctypes.byref(cchReferencedDomainName),
            ctypes.byref(peUse),
        )
        if not success:
            raise ctypes.WinError()
        return pSid, lpReferencedDomainName.value, peUse.value

    def AddAccessAllowedAce(pAcl, dwAceRevision, AccessMask, pSid):
        # add an access-allowed access control entry (ACE)
        # to an access control list (ACL)
        advapi32.AddAccessAllowedAce(pAcl, dwAceRevision, AccessMask, pSid)

    def GetFileSecurity(lpFileName, RequestedInformation):
        # return information about the security of a file or directory
        nLength = wintypes.DWORD(0)
        try:
            advapi32.GetFileSecurityW(
                lpFileName,
                RequestedInformation,
                None,
                0,
                ctypes.byref(nLength),
            )
        except WindowsError as e:
            if e.winerror != ERROR_INSUFFICIENT_BUFFER:
                raise
        if not nLength.value:
            return None
        pSecurityDescriptor = (wintypes.BYTE * nLength.value)()
        advapi32.GetFileSecurityW(
            lpFileName,
            RequestedInformation,
            pSecurityDescriptor,
            nLength,
            ctypes.byref(nLength),
        )
        return pSecurityDescriptor

    def SetFileSecurity(lpFileName, RequestedInformation, pSecurityDescriptor):
        # set the security of a file or directory object
        advapi32.SetFileSecurityW(
            lpFileName, RequestedInformation, pSecurityDescriptor
        )

    def SetSecurityDescriptorDacl(
        pSecurityDescriptor, bDaclPresent, pDacl, bDaclDefaulted
    ):
        # set information in a discretionary access control list (DACL)
        advapi32.SetSecurityDescriptorDacl(
            pSecurityDescriptor, bDaclPresent, pDacl, bDaclDefaulted
        )

    def MakeAbsoluteSD(pSelfRelativeSecurityDescriptor):
        # return a security descriptor in absolute format
        # by using a security descriptor in self-relative format as a template
        pAbsoluteSecurityDescriptor = None
        lpdwAbsoluteSecurityDescriptorSize = wintypes.DWORD(0)
        pDacl = None
        lpdwDaclSize = wintypes.DWORD(0)
        pSacl = None
        lpdwSaclSize = wintypes.DWORD(0)
        pOwner = None
        lpdwOwnerSize = wintypes.DWORD(0)
        pPrimaryGroup = None
        lpdwPrimaryGroupSize = wintypes.DWORD(0)
        try:
            advapi32.MakeAbsoluteSD(
                pSelfRelativeSecurityDescriptor,
                pAbsoluteSecurityDescriptor,
                ctypes.byref(lpdwAbsoluteSecurityDescriptorSize),
                pDacl,
                ctypes.byref(lpdwDaclSize),
                pSacl,
                ctypes.byref(lpdwSaclSize),
                pOwner,
                ctypes.byref(lpdwOwnerSize),
                pPrimaryGroup,
                ctypes.byref(lpdwPrimaryGroupSize),
            )
        except WindowsError as e:
            if e.winerror != ERROR_INSUFFICIENT_BUFFER:
                raise
        pAbsoluteSecurityDescriptor = (
            wintypes.BYTE * lpdwAbsoluteSecurityDescriptorSize.value
        )()
        pDaclData = (wintypes.BYTE * lpdwDaclSize.value)()
        pDacl = ctypes.cast(pDaclData, PACL).contents
        pSaclData = (wintypes.BYTE * lpdwSaclSize.value)()
        pSacl = ctypes.cast(pSaclData, PACL).contents
        pOwnerData = (wintypes.BYTE * lpdwOwnerSize.value)()
        pOwner = ctypes.cast(pOwnerData, PSID)
        pPrimaryGroupData = (wintypes.BYTE * lpdwPrimaryGroupSize.value)()
        pPrimaryGroup = ctypes.cast(pPrimaryGroupData, PSID)
        advapi32.MakeAbsoluteSD(
            pSelfRelativeSecurityDescriptor,
            pAbsoluteSecurityDescriptor,
            ctypes.byref(lpdwAbsoluteSecurityDescriptorSize),
            pDacl,
            ctypes.byref(lpdwDaclSize),
            pSacl,
            ctypes.byref(lpdwSaclSize),
            pOwner,
            lpdwOwnerSize,
            pPrimaryGroup,
            ctypes.byref(lpdwPrimaryGroupSize),
        )
        return pAbsoluteSecurityDescriptor

    def MakeSelfRelativeSD(pAbsoluteSecurityDescriptor):
        # return a security descriptor in self-relative format
        # by using a security descriptor in absolute format as a template
        pSelfRelativeSecurityDescriptor = None
        lpdwBufferLength = wintypes.DWORD(0)
        try:
            advapi32.MakeSelfRelativeSD(
                pAbsoluteSecurityDescriptor,
                pSelfRelativeSecurityDescriptor,
                ctypes.byref(lpdwBufferLength),
            )
        except WindowsError as e:
            if e.winerror != ERROR_INSUFFICIENT_BUFFER:
                raise
        pSelfRelativeSecurityDescriptor = (
            wintypes.BYTE * lpdwBufferLength.value
        )()
        advapi32.MakeSelfRelativeSD(
            pAbsoluteSecurityDescriptor,
            pSelfRelativeSecurityDescriptor,
            ctypes.byref(lpdwBufferLength),
        )
        return pSelfRelativeSecurityDescriptor

    def NewAcl():
        # return a new, initialized ACL (access control list) structure
        nAclLength = 32767  # TODO: calculate this: ctypes.sizeof(ACL) + ?
        acl_data = ctypes.create_string_buffer(nAclLength)
        pAcl = ctypes.cast(acl_data, PACL).contents
        advapi32.InitializeAcl(pAcl, nAclLength, ACL_REVISION)
        return pAcl

    SidAdmins = CreateWellKnownSid(WinBuiltinAdministratorsSid)
    SidUser = LookupAccountName('', GetUserNameEx(NameSamCompatible))[0]

    Acl = NewAcl()
    AddAccessAllowedAce(Acl, ACL_REVISION, FILE_ALL_ACCESS, SidAdmins)
    AddAccessAllowedAce(
        Acl,
        ACL_REVISION,
        FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE,
        SidUser,
    )

    SelfRelativeSD = GetFileSecurity(fname, DACL_SECURITY_INFORMATION)
    AbsoluteSD = MakeAbsoluteSD(SelfRelativeSD)
    SetSecurityDescriptorDacl(AbsoluteSD, 1, Acl, 0)
    SelfRelativeSD = MakeSelfRelativeSD(AbsoluteSD)

    SetFileSecurity(fname, DACL_SECURITY_INFORMATION, SelfRelativeSD)

@cgohlke
Copy link
Contributor

cgohlke commented May 25, 2021

Re AttributeError: FD attribute is write-only: that looks like a bug in the cffi backend in pyzmq. The Cython backend works (build pyzmq with Cython and set the PYZMQ_BACKEND environment variable to 'cython').

@cgohlke
Copy link
Contributor

cgohlke commented May 25, 2021

Re AttributeError: FD attribute is write-only. The pyzmq cffi backend treats SOCKETs as int, while on win_amd64 it should be size_t or similar (UINT_PTR). I'll open a pull request over at pyzmq once it's working.

@stonebig
Copy link

Greaaaaaat ! PyPy3 is now compatible with Jupyter Notebook

image

@cgohlke
Copy link
Contributor

cgohlke commented May 25, 2021

Are you using the Cython backend? With the default cffi backend I get occasional crashes when closing sockets...

@stonebig
Copy link

stonebig commented May 25, 2021

No clue, how do I know that ?
just did 1 test before my working day, to confirm early success

@stonebig
Copy link

stonebig commented May 25, 2021

I don't see instability tonight in my longer experiments,

@mattip
Copy link
Author

mattip commented May 25, 2021

This has drifted quite off-topic for this issue. Please continue the discussion on the PyPy issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants