Blame SOURCES/Add-KCM_OP_GET_CRED_LIST-for-faster-iteration.patch

cb4cef
From 418e64100d1e3f8c8e3f773909347bad270a2921 Mon Sep 17 00:00:00 2001
cb4cef
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
cb4cef
Date: Thu, 11 Feb 2021 15:33:10 +0100
cb4cef
Subject: [PATCH] Add KCM_OP_GET_CRED_LIST for faster iteration
cb4cef
cb4cef
For large caches, one IPC operation per credential dominates the cost
cb4cef
of iteration.  Instead transfer the whole list of credentials to the
cb4cef
client in one IPC operation.
cb4cef
cb4cef
Add optional support for the new opcode to the test KCM server to
cb4cef
allow testing of the main and fallback code paths.
cb4cef
cb4cef
[ghudson@mit.edu: fixed memory leaks and potential memory errors;
cb4cef
adjusted code style and comments; rewrote commit message; added
cb4cef
kcmserver.py support and tests]
cb4cef
cb4cef
ticket: 8990 (new)
cb4cef
(cherry picked from commit 81bdb47d8ded390263d8ee48f71d5c312b4f1736)
cb4cef
(cherry picked from commit a0ee8b02e56c65e5dcd569caed0e151cef004ef4)
cb4cef
---
cb4cef
 src/include/kcm.h            |  12 ++-
cb4cef
 src/lib/krb5/ccache/cc_kcm.c | 144 ++++++++++++++++++++++++++++++++---
cb4cef
 src/tests/kcmserver.py       |  28 ++++++-
cb4cef
 src/tests/t_ccache.py        |  10 ++-
cb4cef
 4 files changed, 175 insertions(+), 19 deletions(-)
cb4cef
cb4cef
diff --git a/src/include/kcm.h b/src/include/kcm.h
cb4cef
index 5ea1447cd..e4140c3a0 100644
cb4cef
--- a/src/include/kcm.h
cb4cef
+++ b/src/include/kcm.h
cb4cef
@@ -51,9 +51,9 @@
cb4cef
  *
cb4cef
  * All replies begin with a 32-bit big-endian reply code.
cb4cef
  *
cb4cef
- * Parameters are appended to the request or reply with no delimiters.  Flags
cb4cef
- * and time offsets are stored as 32-bit big-endian integers.  Names are
cb4cef
- * marshalled as zero-terminated strings.  Principals and credentials are
cb4cef
+ * Parameters are appended to the request or reply with no delimiters.  Flags,
cb4cef
+ * time offsets, and lengths are stored as 32-bit big-endian integers.  Names
cb4cef
+ * are marshalled as zero-terminated strings.  Principals and credentials are
cb4cef
  * marshalled in the v4 FILE ccache format.  UUIDs are 16 bytes.  UUID lists
cb4cef
  * are not delimited, so nothing can come after them.
cb4cef
  */
cb4cef
@@ -89,7 +89,11 @@ typedef enum kcm_opcode {
cb4cef
     KCM_OP_HAVE_NTLM_CRED,
cb4cef
     KCM_OP_DEL_NTLM_CRED,
cb4cef
     KCM_OP_DO_NTLM_AUTH,
cb4cef
-    KCM_OP_GET_NTLM_USER_LIST
cb4cef
+    KCM_OP_GET_NTLM_USER_LIST,
cb4cef
+
cb4cef
+    /* MIT extensions */
cb4cef
+    KCM_OP_MIT_EXTENSION_BASE = 13000,
cb4cef
+    KCM_OP_GET_CRED_LIST,       /* (name) -> (count, count*{len, cred}) */
cb4cef
 } kcm_opcode;
cb4cef
 
cb4cef
 #endif /* KCM_H */
cb4cef
diff --git a/src/lib/krb5/ccache/cc_kcm.c b/src/lib/krb5/ccache/cc_kcm.c
cb4cef
index 9093f894d..772928e4d 100644
cb4cef
--- a/src/lib/krb5/ccache/cc_kcm.c
cb4cef
+++ b/src/lib/krb5/ccache/cc_kcm.c
cb4cef
@@ -61,6 +61,17 @@ struct uuid_list {
cb4cef
     size_t pos;
cb4cef
 };
cb4cef
 
cb4cef
+struct cred_list {
cb4cef
+    krb5_creds *creds;
cb4cef
+    size_t count;
cb4cef
+    size_t pos;
cb4cef
+};
cb4cef
+
cb4cef
+struct kcm_cursor {
cb4cef
+    struct uuid_list *uuids;
cb4cef
+    struct cred_list *creds;
cb4cef
+};
cb4cef
+
cb4cef
 struct kcmio {
cb4cef
     SOCKET fd;
cb4cef
 #ifdef __APPLE__
cb4cef
@@ -489,6 +500,69 @@ free_uuid_list(struct uuid_list *uuids)
cb4cef
     free(uuids);
cb4cef
 }
cb4cef
 
cb4cef
+static void
cb4cef
+free_cred_list(struct cred_list *list)
cb4cef
+{
cb4cef
+    size_t i;
cb4cef
+
cb4cef
+    if (list == NULL)
cb4cef
+        return;
cb4cef
+
cb4cef
+    /* Creds are transferred to the caller as list->pos is incremented, so we
cb4cef
+     * can start freeing there. */
cb4cef
+    for (i = list->pos; i < list->count; i++)
cb4cef
+        krb5_free_cred_contents(NULL, &list->creds[i]);
cb4cef
+    free(list->creds);
cb4cef
+    free(list);
cb4cef
+}
cb4cef
+
cb4cef
+/* Fetch a cred list from req->reply. */
cb4cef
+static krb5_error_code
cb4cef
+kcmreq_get_cred_list(struct kcmreq *req, struct cred_list **creds_out)
cb4cef
+{
cb4cef
+    struct cred_list *list;
cb4cef
+    const unsigned char *data;
cb4cef
+    krb5_error_code ret = 0;
cb4cef
+    size_t count, len, i;
cb4cef
+
cb4cef
+    *creds_out = NULL;
cb4cef
+
cb4cef
+    /* Check a rough bound on the count to prevent very large allocations. */
cb4cef
+    count = k5_input_get_uint32_be(&req->reply);
cb4cef
+    if (count > req->reply.len / 4)
cb4cef
+        return KRB5_KCM_MALFORMED_REPLY;
cb4cef
+
cb4cef
+    list = malloc(sizeof(*list));
cb4cef
+    if (list == NULL)
cb4cef
+        return ENOMEM;
cb4cef
+
cb4cef
+    list->creds = NULL;
cb4cef
+    list->count = count;
cb4cef
+    list->pos = 0;
cb4cef
+    list->creds = k5calloc(count, sizeof(*list->creds), &ret;;
cb4cef
+    if (list->creds == NULL) {
cb4cef
+        free(list);
cb4cef
+        return ret;
cb4cef
+    }
cb4cef
+
cb4cef
+    for (i = 0; i < count; i++) {
cb4cef
+        len = k5_input_get_uint32_be(&req->reply);
cb4cef
+        data = k5_input_get_bytes(&req->reply, len);
cb4cef
+        if (data == NULL)
cb4cef
+            break;
cb4cef
+        ret = k5_unmarshal_cred(data, len, 4, &list->creds[i]);
cb4cef
+        if (ret)
cb4cef
+            break;
cb4cef
+    }
cb4cef
+    if (i < count) {
cb4cef
+        free_cred_list(list);
cb4cef
+        return (ret == ENOMEM) ? ENOMEM : KRB5_KCM_MALFORMED_REPLY;
cb4cef
+    }
cb4cef
+
cb4cef
+    *creds_out = list;
cb4cef
+    return 0;
cb4cef
+}
cb4cef
+
cb4cef
 static void
cb4cef
 kcmreq_free(struct kcmreq *req)
cb4cef
 {
cb4cef
@@ -753,33 +827,53 @@ kcm_start_seq_get(krb5_context context, krb5_ccache cache,
cb4cef
 {
cb4cef
     krb5_error_code ret;
cb4cef
     struct kcmreq req = EMPTY_KCMREQ;
cb4cef
-    struct uuid_list *uuids;
cb4cef
+    struct uuid_list *uuids = NULL;
cb4cef
+    struct cred_list *creds = NULL;
cb4cef
+    struct kcm_cursor *cursor;
cb4cef
 
cb4cef
     *cursor_out = NULL;
cb4cef
 
cb4cef
     get_kdc_offset(context, cache);
cb4cef
 
cb4cef
-    kcmreq_init(&req, KCM_OP_GET_CRED_UUID_LIST, cache);
cb4cef
+    kcmreq_init(&req, KCM_OP_GET_CRED_LIST, cache);
cb4cef
     ret = cache_call(context, cache, &req;;
cb4cef
-    if (ret)
cb4cef
+    if (ret == 0) {
cb4cef
+        /* GET_CRED_LIST is available. */
cb4cef
+        ret = kcmreq_get_cred_list(&req, &creds);
cb4cef
+        if (ret)
cb4cef
+            goto cleanup;
cb4cef
+    } else if (ret == KRB5_FCC_INTERNAL) {
cb4cef
+        /* Fall back to GET_CRED_UUID_LIST. */
cb4cef
+        kcmreq_free(&req;;
cb4cef
+        kcmreq_init(&req, KCM_OP_GET_CRED_UUID_LIST, cache);
cb4cef
+        ret = cache_call(context, cache, &req;;
cb4cef
+        if (ret)
cb4cef
+            goto cleanup;
cb4cef
+        ret = kcmreq_get_uuid_list(&req, &uuids);
cb4cef
+        if (ret)
cb4cef
+            goto cleanup;
cb4cef
+    } else {
cb4cef
         goto cleanup;
cb4cef
-    ret = kcmreq_get_uuid_list(&req, &uuids);
cb4cef
-    if (ret)
cb4cef
+    }
cb4cef
+
cb4cef
+    cursor = k5alloc(sizeof(*cursor), &ret;;
cb4cef
+    if (cursor == NULL)
cb4cef
         goto cleanup;
cb4cef
-    *cursor_out = (krb5_cc_cursor)uuids;
cb4cef
+    cursor->uuids = uuids;
cb4cef
+    cursor->creds = creds;
cb4cef
+    *cursor_out = (krb5_cc_cursor)cursor;
cb4cef
 
cb4cef
 cleanup:
cb4cef
     kcmreq_free(&req;;
cb4cef
     return ret;
cb4cef
 }
cb4cef
 
cb4cef
-static krb5_error_code KRB5_CALLCONV
cb4cef
-kcm_next_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor,
cb4cef
-              krb5_creds *cred_out)
cb4cef
+static krb5_error_code
cb4cef
+next_cred_by_uuid(krb5_context context, krb5_ccache cache,
cb4cef
+                  struct uuid_list *uuids, krb5_creds *cred_out)
cb4cef
 {
cb4cef
     krb5_error_code ret;
cb4cef
     struct kcmreq req;
cb4cef
-    struct uuid_list *uuids = (struct uuid_list *)*cursor;
cb4cef
 
cb4cef
     memset(cred_out, 0, sizeof(*cred_out));
cb4cef
 
cb4cef
@@ -797,11 +891,39 @@ kcm_next_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor,
cb4cef
     return map_invalid(ret);
cb4cef
 }
cb4cef
 
cb4cef
+static krb5_error_code KRB5_CALLCONV
cb4cef
+kcm_next_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor,
cb4cef
+              krb5_creds *cred_out)
cb4cef
+{
cb4cef
+    struct kcm_cursor *c = (struct kcm_cursor *)*cursor;
cb4cef
+    struct cred_list *list;
cb4cef
+
cb4cef
+    if (c->uuids != NULL)
cb4cef
+        return next_cred_by_uuid(context, cache, c->uuids, cred_out);
cb4cef
+
cb4cef
+    list = c->creds;
cb4cef
+    if (list->pos >= list->count)
cb4cef
+        return KRB5_CC_END;
cb4cef
+
cb4cef
+    /* Transfer memory ownership of one cred to the caller. */
cb4cef
+    *cred_out = list->creds[list->pos];
cb4cef
+    memset(&list->creds[list->pos], 0, sizeof(*list->creds));
cb4cef
+    list->pos++;
cb4cef
+
cb4cef
+    return 0;
cb4cef
+}
cb4cef
+
cb4cef
 static krb5_error_code KRB5_CALLCONV
cb4cef
 kcm_end_seq_get(krb5_context context, krb5_ccache cache,
cb4cef
                 krb5_cc_cursor *cursor)
cb4cef
 {
cb4cef
-    free_uuid_list((struct uuid_list *)*cursor);
cb4cef
+    struct kcm_cursor *c = *cursor;
cb4cef
+
cb4cef
+    if (c == NULL)
cb4cef
+        return 0;
cb4cef
+    free_uuid_list(c->uuids);
cb4cef
+    free_cred_list(c->creds);
cb4cef
+    free(c);
cb4cef
     *cursor = NULL;
cb4cef
     return 0;
cb4cef
 }
cb4cef
diff --git a/src/tests/kcmserver.py b/src/tests/kcmserver.py
cb4cef
index 57432e5a7..8c5e66ff1 100644
cb4cef
--- a/src/tests/kcmserver.py
cb4cef
+++ b/src/tests/kcmserver.py
cb4cef
@@ -23,6 +23,7 @@
cb4cef
 #         traceback.print_exception(etype, value, tb, file=f)
cb4cef
 # sys.excepthook = ehook
cb4cef
 
cb4cef
+import optparse
cb4cef
 import select
cb4cef
 import socket
cb4cef
 import struct
cb4cef
@@ -49,12 +50,14 @@ class KCMOpcodes(object):
cb4cef
     SET_DEFAULT_CACHE = 21
cb4cef
     GET_KDC_OFFSET = 22
cb4cef
     SET_KDC_OFFSET = 23
cb4cef
+    GET_CRED_LIST = 13001
cb4cef
 
cb4cef
 
cb4cef
 class KRB5Errors(object):
cb4cef
     KRB5_CC_END = -1765328242
cb4cef
     KRB5_CC_NOSUPP = -1765328137
cb4cef
     KRB5_FCC_NOFILE = -1765328189
cb4cef
+    KRB5_FCC_INTERNAL = -1765328188
cb4cef
 
cb4cef
 
cb4cef
 def make_uuid():
cb4cef
@@ -183,6 +186,14 @@ def op_set_kdc_offset(argbytes):
cb4cef
     return 0, b''
cb4cef
 
cb4cef
 
cb4cef
+def op_get_cred_list(argbytes):
cb4cef
+    name, rest = unmarshal_name(argbytes)
cb4cef
+    cache = get_cache(name)
cb4cef
+    creds = [cache.creds[u] for u in cache.cred_uuids]
cb4cef
+    return 0, (struct.pack('>L', len(creds)) +
cb4cef
+               b''.join(struct.pack('>L', len(c)) + c for c in creds))
cb4cef
+
cb4cef
+
cb4cef
 ophandlers = {
cb4cef
     KCMOpcodes.GEN_NEW : op_gen_new,
cb4cef
     KCMOpcodes.INITIALIZE : op_initialize,
cb4cef
@@ -197,7 +208,8 @@ ophandlers = {
cb4cef
     KCMOpcodes.GET_DEFAULT_CACHE : op_get_default_cache,
cb4cef
     KCMOpcodes.SET_DEFAULT_CACHE : op_set_default_cache,
cb4cef
     KCMOpcodes.GET_KDC_OFFSET : op_get_kdc_offset,
cb4cef
-    KCMOpcodes.SET_KDC_OFFSET : op_set_kdc_offset
cb4cef
+    KCMOpcodes.SET_KDC_OFFSET : op_set_kdc_offset,
cb4cef
+    KCMOpcodes.GET_CRED_LIST : op_get_cred_list
cb4cef
 }
cb4cef
 
cb4cef
 # Read and respond to a request from the socket s.
cb4cef
@@ -215,7 +227,11 @@ def service_request(s):
cb4cef
 
cb4cef
     majver, minver, op = struct.unpack('>BBH', req[:4])
cb4cef
     argbytes = req[4:]
cb4cef
-    code, payload = ophandlers[op](argbytes)
cb4cef
+
cb4cef
+    if op in ophandlers:
cb4cef
+        code, payload = ophandlers[op](argbytes)
cb4cef
+    else:
cb4cef
+        code, payload = KRB5Errors.KRB5_FCC_INTERNAL, b''
cb4cef
 
cb4cef
     # The KCM response is the code (4 bytes) and the response payload.
cb4cef
     # The Heimdal IPC response is the length of the KCM response (4
cb4cef
@@ -226,9 +242,15 @@ def service_request(s):
cb4cef
     s.sendall(hipc_response)
cb4cef
     return True
cb4cef
 
cb4cef
+parser = optparse.OptionParser()
cb4cef
+parser.add_option('-c', '--credlist', action='store_true', dest='credlist',
cb4cef
+                  default=False, help='Support KCM_OP_GET_CRED_LIST')
cb4cef
+(options, args) = parser.parse_args()
cb4cef
+if not options.credlist:
cb4cef
+    del ophandlers[KCMOpcodes.GET_CRED_LIST]
cb4cef
 
cb4cef
 server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
cb4cef
-server.bind(sys.argv[1])
cb4cef
+server.bind(args[0])
cb4cef
 server.listen(5)
cb4cef
 select_input = [server,]
cb4cef
 sys.stderr.write('starting...\n')
cb4cef
diff --git a/src/tests/t_ccache.py b/src/tests/t_ccache.py
cb4cef
index 66804afa5..90040fb7b 100755
cb4cef
--- a/src/tests/t_ccache.py
cb4cef
+++ b/src/tests/t_ccache.py
cb4cef
@@ -125,10 +125,18 @@ def collection_test(realm, ccname):
cb4cef
 
cb4cef
 
cb4cef
 collection_test(realm, 'DIR:' + os.path.join(realm.testdir, 'cc'))
cb4cef
+
cb4cef
+# Test KCM without and with GET_CRED_LIST support.
cb4cef
 kcmserver_path = os.path.join(srctop, 'tests', 'kcmserver.py')
cb4cef
-realm.start_server([sys.executable, kcmserver_path, kcm_socket_path],
cb4cef
+kcmd = realm.start_server([sys.executable, kcmserver_path, kcm_socket_path],
cb4cef
+                          'starting...')
cb4cef
+collection_test(realm, 'KCM:')
cb4cef
+stop_daemon(kcmd)
cb4cef
+os.remove(kcm_socket_path)
cb4cef
+realm.start_server([sys.executable, kcmserver_path, '-c', kcm_socket_path],
cb4cef
                    'starting...')
cb4cef
 collection_test(realm, 'KCM:')
cb4cef
+
cb4cef
 if test_keyring:
cb4cef
     def cleanup_keyring(anchor, name):
cb4cef
         out = realm.run(['keyctl', 'list', anchor])