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

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