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

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