bpo-39007: Add auditing events to functions in winreg (GH-17541)

Also allows winreg.CloseKey() to accept same types as other functions.
diff --git a/Doc/library/winreg.rst b/Doc/library/winreg.rst
index 5e81068..dccb7db 100644
--- a/Doc/library/winreg.rst
+++ b/Doc/library/winreg.rst
@@ -53,6 +53,8 @@
    The return value is the handle of the opened key. If the function fails, an
    :exc:`OSError` exception is raised.
 
+   .. audit-event:: winreg.ConnectRegistry computer_name,key winreg.ConnectRegistry
+
    .. versionchanged:: 3.3
       See :ref:`above <exception-changed>`.
 
@@ -75,6 +77,10 @@
    The return value is the handle of the opened key. If the function fails, an
    :exc:`OSError` exception is raised.
 
+   .. audit-event:: winreg.CreateKey key,sub_key,access winreg.CreateKey
+
+   .. audit-event:: winreg.OpenKey/result key winreg.CreateKey
+
    .. versionchanged:: 3.3
       See :ref:`above <exception-changed>`.
 
@@ -103,6 +109,10 @@
    The return value is the handle of the opened key. If the function fails, an
    :exc:`OSError` exception is raised.
 
+   .. audit-event:: winreg.CreateKey key,sub_key,access winreg.CreateKeyEx
+
+   .. audit-event:: winreg.OpenKey/result key winreg.CreateKeyEx
+
    .. versionadded:: 3.2
 
    .. versionchanged:: 3.3
@@ -124,6 +134,8 @@
    If the method succeeds, the entire key, including all of its values, is removed.
    If the method fails, an :exc:`OSError` exception is raised.
 
+   .. audit-event:: winreg.DeleteKey key,sub_key,access winreg.DeleteKey
+
    .. versionchanged:: 3.3
       See :ref:`above <exception-changed>`.
 
@@ -158,6 +170,8 @@
 
    On unsupported Windows versions, :exc:`NotImplementedError` is raised.
 
+   .. audit-event:: winreg.DeleteKey key,sub_key,access winreg.DeleteKeyEx
+
    .. versionadded:: 3.2
 
    .. versionchanged:: 3.3
@@ -173,6 +187,8 @@
 
    *value* is a string that identifies the value to remove.
 
+   .. audit-event:: winreg.DeleteValue key,value winreg.DeleteValue
+
 
 .. function:: EnumKey(key, index)
 
@@ -187,6 +203,8 @@
    typically called repeatedly until an :exc:`OSError` exception is
    raised, indicating, no more values are available.
 
+   .. audit-event:: winreg.EnumKey key,index winreg.EnumKey
+
    .. versionchanged:: 3.3
       See :ref:`above <exception-changed>`.
 
@@ -220,6 +238,8 @@
    |       | :meth:`SetValueEx`)                        |
    +-------+--------------------------------------------+
 
+   .. audit-event:: winreg.EnumValue key,index winreg.EnumValue
+
    .. versionchanged:: 3.3
       See :ref:`above <exception-changed>`.
 
@@ -235,6 +255,8 @@
       >>> ExpandEnvironmentStrings('%windir%')
       'C:\\Windows'
 
+   .. audit-event:: winreg.ExpandEnvironmentStrings str winreg.ExpandEnvironmentStrings
+
 
 .. function:: FlushKey(key)
 
@@ -279,6 +301,8 @@
    If *key* is a handle returned by :func:`ConnectRegistry`, then the path
    specified in *file_name* is relative to the remote computer.
 
+   .. audit-event:: winreg.LoadKey key,sub_key,file_name winreg.LoadKey
+
 
 .. function:: OpenKey(key, sub_key, reserved=0, access=KEY_READ)
               OpenKeyEx(key, sub_key, reserved=0, access=KEY_READ)
@@ -300,6 +324,10 @@
 
    If the function fails, :exc:`OSError` is raised.
 
+   .. audit-event:: winreg.OpenKey key,sub_key,access winreg.OpenKey
+
+   .. audit-event:: winreg.OpenKey/result key winreg.OpenKey
+
    .. versionchanged:: 3.2
       Allow the use of named arguments.
 
@@ -330,6 +358,8 @@
    |       | nanoseconds since Jan 1, 1601.              |
    +-------+---------------------------------------------+
 
+   .. audit-event:: winreg.QueryInfoKey key winreg.QueryInfoKey
+
 
 .. function:: QueryValue(key, sub_key)
 
@@ -347,6 +377,8 @@
    underlying API call doesn't return the type, so always use
    :func:`QueryValueEx` if possible.
 
+   .. audit-event:: winreg.QueryValue key,sub_key,value_name winreg.QueryValue
+
 
 .. function:: QueryValueEx(key, value_name)
 
@@ -370,6 +402,8 @@
    |       | :meth:`SetValueEx`)                     |
    +-------+-----------------------------------------+
 
+   .. audit-event:: winreg.QueryValue key,sub_key,value_name winreg.QueryValueEx
+
 
 .. function:: SaveKey(key, file_name)
 
@@ -393,6 +427,8 @@
 
    This function passes ``NULL`` for *security_attributes* to the API.
 
+   .. audit-event:: winreg.SaveKey key,file_name winreg.SaveKey
+
 
 .. function:: SetValue(key, sub_key, type, value)
 
@@ -419,6 +455,8 @@
    The key identified by the *key* parameter must have been opened with
    :const:`KEY_SET_VALUE` access.
 
+   .. audit-event:: winreg.SetValue key,sub_key,type,value winreg.SetValue
+
 
 .. function:: SetValueEx(key, value_name, reserved, type, value)
 
@@ -447,6 +485,8 @@
    bytes) should be stored as files with the filenames stored in the configuration
    registry.  This helps the registry perform efficiently.
 
+   .. audit-event:: winreg.SetValue key,sub_key,type,value winreg.SetValueEx
+
 
 .. function:: DisableReflectionKey(key)
 
@@ -463,6 +503,8 @@
    effect.  Disabling reflection for a key does not affect reflection of any
    subkeys.
 
+   .. audit-event:: winreg.DisableReflectionKey key winreg.DisableReflectionKey
+
 
 .. function:: EnableReflectionKey(key)
 
@@ -476,6 +518,8 @@
 
    Restoring reflection for a key does not affect reflection of any subkeys.
 
+   .. audit-event:: winreg.EnableReflectionKey key winreg.EnableReflectionKey
+
 
 .. function:: QueryReflectionKey(key)
 
@@ -489,6 +533,8 @@
    Will generally raise :exc:`NotImplementedError` if executed on a 32-bit
    operating system.
 
+   .. audit-event:: winreg.QueryReflectionKey key winreg.QueryReflectionKey
+
 
 .. _constants:
 
@@ -741,6 +787,9 @@
    handle is not closed.  You would call this function when you need the
    underlying Win32 handle to exist beyond the lifetime of the handle object.
 
+   .. audit-event:: winreg.PyHKEY.Detach key winreg.PyHKEY.Detach
+
+
 .. method:: PyHKEY.__enter__()
             PyHKEY.__exit__(\*exc_info)
 
diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py
index ed08612..33f3209 100644
--- a/Lib/test/audit-tests.py
+++ b/Lib/test/audit-tests.py
@@ -304,6 +304,29 @@
     write_unraisable_exc(RuntimeError("nonfatal-error"), "for audit hook test", None)
 
 
+def test_winreg():
+    from winreg import OpenKey, EnumKey, CloseKey, HKEY_LOCAL_MACHINE
+
+    def hook(event, args):
+        if not event.startswith("winreg."):
+            return
+        print(event, *args)
+
+    sys.addaudithook(hook)
+
+    k = OpenKey(HKEY_LOCAL_MACHINE, "Software")
+    EnumKey(k, 0)
+    try:
+        EnumKey(k, 10000)
+    except OSError:
+        pass
+    else:
+        raise RuntimeError("Expected EnumKey(HKLM, 10000) to fail")
+
+    kv = k.Detach()
+    CloseKey(kv)
+
+
 if __name__ == "__main__":
     from test.libregrtest.setup import suppress_msvcrt_asserts
 
diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py
index 31a0855..73dd5c5 100644
--- a/Lib/test/test_audit.py
+++ b/Lib/test/test_audit.py
@@ -104,6 +104,20 @@
             "RuntimeError('nonfatal-error') Exception ignored for audit hook test",
         )
 
+    def test_winreg(self):
+        support.import_module("winreg")
+        returncode, events, stderr = self.run_python("test_winreg")
+        if returncode:
+            self.fail(stderr)
+
+        self.assertEqual(events[0][0], "winreg.OpenKey")
+        self.assertEqual(events[1][0], "winreg.OpenKey/result")
+        expected = events[1][2]
+        self.assertTrue(expected)
+        self.assertSequenceEqual(["winreg.EnumKey", " ", f"{expected} 0"], events[2])
+        self.assertSequenceEqual(["winreg.EnumKey", " ", f"{expected} 10000"], events[3])
+        self.assertSequenceEqual(["winreg.PyHKEY.Detach", " ", expected], events[4])
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Windows/2019-12-09-10-40-34.bpo-39007.vtarxo.rst b/Misc/NEWS.d/next/Windows/2019-12-09-10-40-34.bpo-39007.vtarxo.rst
new file mode 100644
index 0000000..f2f72f9
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2019-12-09-10-40-34.bpo-39007.vtarxo.rst
@@ -0,0 +1 @@
+Add auditing events to functions in :mod:`winreg`.
diff --git a/PC/winreg.c b/PC/winreg.c
index 72a7c38..5dff7de 100644
--- a/PC/winreg.c
+++ b/PC/winreg.c
@@ -293,6 +293,9 @@
 /*[clinic end generated code: output=dda5a9e1a01ae78f input=dd2cc09e6c6ba833]*/
 {
     void* ret;
+    if (PySys_Audit("winreg.PyHKEY.Detach", "n", (Py_ssize_t)self->hkey) < 0) {
+        return NULL;
+    }
     ret = (void*)self->hkey;
     self->hkey = 0;
     return PyLong_FromVoidPtr(ret);
@@ -397,15 +400,15 @@
 PyHKEY_Close(PyObject *ob_handle)
 {
     LONG rc;
-    PyHKEYObject *key;
+    HKEY key;
 
-    if (!PyHKEY_Check(ob_handle)) {
-        PyErr_SetString(PyExc_TypeError, "bad operand type");
+    if (!PyHKEY_AsHKEY(ob_handle, &key, TRUE)) {
         return FALSE;
     }
-    key = (PyHKEYObject *)ob_handle;
-    rc = key->hkey ? RegCloseKey((HKEY)key->hkey) : ERROR_SUCCESS;
-    key->hkey = 0;
+    if (PyHKEY_Check(ob_handle)) {
+        ((PyHKEYObject*)ob_handle)->hkey = 0;
+    }
+    rc = key ? RegCloseKey(key) : ERROR_SUCCESS;
     if (rc != ERROR_SUCCESS)
         PyErr_SetFromWindowsErrWithFunction(rc, "RegCloseKey");
     return rc == ERROR_SUCCESS;
@@ -841,6 +844,10 @@
 {
     HKEY retKey;
     long rc;
+    if (PySys_Audit("winreg.ConnectRegistry", "un",
+                    computer_name, (Py_ssize_t)key) < 0) {
+        return NULL;
+    }
     Py_BEGIN_ALLOW_THREADS
     rc = RegConnectRegistryW(computer_name, key, &retKey);
     Py_END_ALLOW_THREADS
@@ -878,11 +885,20 @@
     HKEY retKey;
     long rc;
 
+    if (PySys_Audit("winreg.CreateKey", "nun",
+                    (Py_ssize_t)key, sub_key,
+                    (Py_ssize_t)KEY_WRITE) < 0) {
+        return NULL;
+    }
     rc = RegCreateKeyW(key, sub_key, &retKey);
     if (rc != ERROR_SUCCESS) {
         PyErr_SetFromWindowsErrWithFunction(rc, "CreateKey");
         return NULL;
     }
+    if (PySys_Audit("winreg.OpenKey/result", "n",
+                    (Py_ssize_t)retKey) < 0) {
+        return NULL;
+    }
     return retKey;
 }
 
@@ -919,12 +935,21 @@
     HKEY retKey;
     long rc;
 
+    if (PySys_Audit("winreg.CreateKey", "nun",
+                    (Py_ssize_t)key, sub_key,
+                    (Py_ssize_t)access) < 0) {
+        return NULL;
+    }
     rc = RegCreateKeyExW(key, sub_key, reserved, NULL, 0,
                          access, NULL, &retKey, NULL);
     if (rc != ERROR_SUCCESS) {
         PyErr_SetFromWindowsErrWithFunction(rc, "CreateKeyEx");
         return NULL;
     }
+    if (PySys_Audit("winreg.OpenKey/result", "n",
+                    (Py_ssize_t)retKey) < 0) {
+        return NULL;
+    }
     return retKey;
 }
 
@@ -951,6 +976,11 @@
 /*[clinic end generated code: output=d2652a84f70e0862 input=b31d225b935e4211]*/
 {
     long rc;
+    if (PySys_Audit("winreg.DeleteKey", "nun",
+                    (Py_ssize_t)key, sub_key,
+                    (Py_ssize_t)0) < 0) {
+        return NULL;
+    }
     rc = RegDeleteKeyW(key, sub_key );
     if (rc != ERROR_SUCCESS)
         return PyErr_SetFromWindowsErrWithFunction(rc, "RegDeleteKey");
@@ -992,13 +1022,17 @@
     RDKEFunc pfn = NULL;
     long rc;
 
+    if (PySys_Audit("winreg.DeleteKey", "nun",
+                    (Py_ssize_t)key, sub_key,
+                    (Py_ssize_t)access) < 0) {
+        return NULL;
+    }
     /* Only available on 64bit platforms, so we must load it
        dynamically. */
     Py_BEGIN_ALLOW_THREADS
     hMod = GetModuleHandleW(L"advapi32.dll");
     if (hMod)
-        pfn = (RDKEFunc)GetProcAddress(hMod,
-                                                                   "RegDeleteKeyExW");
+        pfn = (RDKEFunc)GetProcAddress(hMod, "RegDeleteKeyExW");
     Py_END_ALLOW_THREADS
     if (!pfn) {
         PyErr_SetString(PyExc_NotImplementedError,
@@ -1031,6 +1065,10 @@
 /*[clinic end generated code: output=56fa9d21f3a54371 input=a78d3407a4197b21]*/
 {
     long rc;
+    if (PySys_Audit("winreg.DeleteValue", "nu",
+                    (Py_ssize_t)key, value) < 0) {
+        return NULL;
+    }
     Py_BEGIN_ALLOW_THREADS
     rc = RegDeleteValueW(key, value);
     Py_END_ALLOW_THREADS
@@ -1063,6 +1101,10 @@
     long rc;
     PyObject *retStr;
 
+    if (PySys_Audit("winreg.EnumKey", "ni",
+                    (Py_ssize_t)key, index) < 0) {
+        return NULL;
+    }
     /* The Windows docs claim that the max key name length is 255
      * characters, plus a terminating nul character.  However,
      * empirical testing demonstrates that it is possible to
@@ -1121,6 +1163,10 @@
     PyObject *obData;
     PyObject *retVal;
 
+    if (PySys_Audit("winreg.EnumValue", "ni",
+                    (Py_ssize_t)key, index) < 0) {
+        return NULL;
+    }
     if ((rc = RegQueryInfoKeyW(key, NULL, NULL, NULL, NULL, NULL, NULL,
                               NULL,
                               &retValueSize, &retDataSize, NULL, NULL))
@@ -1204,6 +1250,11 @@
     DWORD rc;
     PyObject *o;
 
+    if (PySys_Audit("winreg.ExpandEnvironmentStrings", "u",
+                    string) < 0) {
+        return NULL;
+    }
+
     retValueSize = ExpandEnvironmentStringsW(string, retValue, 0);
     if (retValueSize == 0) {
         return PyErr_SetFromWindowsErrWithFunction(retValueSize,
@@ -1295,6 +1346,10 @@
 {
     long rc;
 
+    if (PySys_Audit("winreg.LoadKey", "nuu",
+                    (Py_ssize_t)key, sub_key, file_name) < 0) {
+        return NULL;
+    }
     Py_BEGIN_ALLOW_THREADS
     rc = RegLoadKeyW(key, sub_key, file_name );
     Py_END_ALLOW_THREADS
@@ -1330,6 +1385,11 @@
     HKEY retKey;
     long rc;
 
+    if (PySys_Audit("winreg.OpenKey", "nun",
+                    (Py_ssize_t)key, sub_key,
+                    (Py_ssize_t)access) < 0) {
+        return NULL;
+    }
     Py_BEGIN_ALLOW_THREADS
     rc = RegOpenKeyExW(key, sub_key, reserved, access, &retKey);
     Py_END_ALLOW_THREADS
@@ -1337,6 +1397,10 @@
         PyErr_SetFromWindowsErrWithFunction(rc, "RegOpenKeyEx");
         return NULL;
     }
+    if (PySys_Audit("winreg.OpenKey/result", "n",
+                    (Py_ssize_t)retKey) < 0) {
+        return NULL;
+    }
     return retKey;
 }
 
@@ -1377,25 +1441,30 @@
 winreg_QueryInfoKey_impl(PyObject *module, HKEY key)
 /*[clinic end generated code: output=dc657b8356a4f438 input=c3593802390cde1f]*/
 {
-  long rc;
-  DWORD nSubKeys, nValues;
-  FILETIME ft;
-  LARGE_INTEGER li;
-  PyObject *l;
-  PyObject *ret;
+    long rc;
+    DWORD nSubKeys, nValues;
+    FILETIME ft;
+    LARGE_INTEGER li;
+    PyObject *l;
+    PyObject *ret;
 
-  if ((rc = RegQueryInfoKey(key, NULL, NULL, 0, &nSubKeys, NULL, NULL,
-                            &nValues,  NULL,  NULL, NULL, &ft))
-      != ERROR_SUCCESS)
-    return PyErr_SetFromWindowsErrWithFunction(rc, "RegQueryInfoKey");
-  li.LowPart = ft.dwLowDateTime;
-  li.HighPart = ft.dwHighDateTime;
-  l = PyLong_FromLongLong(li.QuadPart);
-  if (l == NULL)
-    return NULL;
-  ret = Py_BuildValue("iiO", nSubKeys, nValues, l);
-  Py_DECREF(l);
-  return ret;
+    if (PySys_Audit("winreg.QueryInfoKey", "n", (Py_ssize_t)key) < 0) {
+        return NULL;
+    }
+    if ((rc = RegQueryInfoKey(key, NULL, NULL, 0, &nSubKeys, NULL, NULL,
+                              &nValues,  NULL,  NULL, NULL, &ft))
+                              != ERROR_SUCCESS) {
+        return PyErr_SetFromWindowsErrWithFunction(rc, "RegQueryInfoKey");
+    }
+    li.LowPart = ft.dwLowDateTime;
+    li.HighPart = ft.dwHighDateTime;
+    l = PyLong_FromLongLong(li.QuadPart);
+    if (l == NULL) {
+        return NULL;
+    }
+    ret = Py_BuildValue("iiO", nSubKeys, nValues, l);
+    Py_DECREF(l);
+    return ret;
 }
 
 /*[clinic input]
@@ -1430,6 +1499,10 @@
     DWORD retSize = 0;
     wchar_t *tmp;
 
+    if (PySys_Audit("winreg.QueryValue", "nuu",
+                    (Py_ssize_t)key, sub_key, NULL) < 0) {
+        return NULL;
+    }
     rc = RegQueryValueW(key, sub_key, NULL, &retSize);
     if (rc == ERROR_MORE_DATA)
         retSize = 256;
@@ -1497,6 +1570,10 @@
     PyObject *obData;
     PyObject *result;
 
+    if (PySys_Audit("winreg.QueryValue", "nuu",
+                    (Py_ssize_t)key, NULL, name) < 0) {
+        return NULL;
+    }
     rc = RegQueryValueExW(key, name, NULL, NULL, NULL, &bufSize);
     if (rc == ERROR_MORE_DATA)
         bufSize = 256;
@@ -1570,6 +1647,10 @@
     if (!PyWinObject_AsSECURITY_ATTRIBUTES(obSA, &pSA, TRUE))
         return NULL;
 */
+    if (PySys_Audit("winreg.SaveKey", "nu",
+                    (Py_ssize_t)key, file_name) < 0) {
+        return NULL;
+    }
     Py_BEGIN_ALLOW_THREADS
     rc = RegSaveKeyW(key, file_name, pSA );
     Py_END_ALLOW_THREADS
@@ -1622,6 +1703,12 @@
         return NULL;
     }
 
+    if (PySys_Audit("winreg.SetValue", "nunu#",
+                    (Py_ssize_t)key, sub_key, (Py_ssize_t)type,
+                    value, value_length) < 0) {
+        return NULL;
+    }
+
     Py_BEGIN_ALLOW_THREADS
     rc = RegSetValueW(key, sub_key, REG_SZ, value, (DWORD)(value_length + 1));
     Py_END_ALLOW_THREADS
@@ -1692,6 +1779,11 @@
                      "Could not convert the data to the specified type.");
         return NULL;
     }
+    if (PySys_Audit("winreg.SetValue", "nunO",
+                    (Py_ssize_t)key, value_name, (Py_ssize_t)type,
+                    value) < 0) {
+        return NULL;
+    }
     Py_BEGIN_ALLOW_THREADS
     rc = RegSetValueExW(key, value_name, 0, type, data, len);
     Py_END_ALLOW_THREADS
@@ -1727,6 +1819,10 @@
     RDRKFunc pfn = NULL;
     LONG rc;
 
+    if (PySys_Audit("winreg.DisableReflectionKey", "n", (Py_ssize_t)key) < 0) {
+        return NULL;
+    }
+
     /* Only available on 64bit platforms, so we must load it
        dynamically.*/
     Py_BEGIN_ALLOW_THREADS
@@ -1772,6 +1868,10 @@
     RERKFunc pfn = NULL;
     LONG rc;
 
+    if (PySys_Audit("winreg.EnableReflectionKey", "n", (Py_ssize_t)key) < 0) {
+        return NULL;
+    }
+
     /* Only available on 64bit platforms, so we must load it
        dynamically.*/
     Py_BEGIN_ALLOW_THREADS
@@ -1816,6 +1916,10 @@
     BOOL result;
     LONG rc;
 
+    if (PySys_Audit("winreg.QueryReflectionKey", "n", (Py_ssize_t)key) < 0) {
+        return NULL;
+    }
+
     /* Only available on 64bit platforms, so we must load it
        dynamically.*/
     Py_BEGIN_ALLOW_THREADS