Repository: valkey-io/valkey
File: src/acl.c (C)

Code before:
        listIter li;
        listNode *ln;

        listRewind(server.clients, &li);
        while ((ln = listNext(&li)) != NULL) {
            client *c = listNodeValue(ln);
            /* Some clients, e.g. the one from the primary to replica, don't have a user
             * associated with them. */
            if (!c->user) continue;
            user *original = c->user;
            list *channels = NULL;
            user *new_user = ACLGetUserByName(c->user->name, sdslen(c->user->name));
            if (new_user && user_channels) {
                if (!raxFind(user_channels, (unsigned char *)(new_user->name), sdslen(new_user->name),
                             (void **)&channels)) {
                    channels = getUpcomingChannelList(new_user, original);
                    raxInsert(user_channels, (unsigned char *)(new_user->name), sdslen(new_user->name), channels, NULL);
                }
            }
            /* When the new channel list is NULL, it means the new user's channel list is a superset of the old user's
             * list. */
            if (!new_user || (channels && ACLShouldKillPubsubClient(c, channels))) {
                if (c == server.current_client) {
                    c->flag.close_after_command = 1;
                } else {
                    freeClient(c);
                }
                continue;
            }
            c->user = new_user;
        }

        if (user_channels) raxFreeWithCallback(user_channels, listReleaseVoid);
        raxFreeWithCallback(old_users, ACLFreeUserVoid);
        sdsfree(errors);
        return NULL;
    } else {
        raxFreeWithCallback(Users, ACLFreeUserVoid);
        Users = old_users;
        errors =
            sdscat(errors, "WARNING: ACL errors detected, no change to the previously active ACL rules was performed");
        return errors;
    }
}

/* Generate a copy of the ACLs currently in memory in the specified filename.
 * Returns C_OK on success or C_ERR if there was an error during the I/O.
 * When C_ERR is returned a log is produced with hints about the issue. */
static int ACLSaveToFile(const char *filename) {
    sds acl = sdsempty();
    int fd = -1;

Review comment (bug):
This also trivially crashes through the setuser pathway as well:
```
not connected> acl setuser bob on nopass +@all &test
OK
127.0.0.1:6379> auth bob
(error) ERR AUTH <password> called without any password configured for the default user. Are you sure your configuration is correct?
127.0.0.1:6379> auth bob k
OK
127.0.0.1:6379> subscribe bob
(error) NOPERM No permissions to access a channel
127.0.0.1:6379> subscribe test
1) "subscribe"
2) "test"
3) (integer) 1
127.0.0.1:6379(subscribed mode)> acl setuser bob resetchannels
Error: Server closed the connection
```

Code after:

        listRewind(server.clients, &li);
        while ((ln = listNext(&li)) != NULL) {
            client *c = listNodeValue(ln);
            /* Some clients, e.g. the one from the primary to replica, don't have a user
             * associated with them. */
            if (!c->user) continue;
            user *original = c->user;
            list *channels = NULL;
            user *new_user = ACLGetUserByName(c->user->name, sdslen(c->user->name));
            if (new_user && user_channels) {
                if (!raxFind(user_channels, (unsigned char *)(new_user->name), sdslen(new_user->name),
                             (void **)&channels)) {
                    channels = getUpcomingChannelList(new_user, original);
                    raxInsert(user_channels, (unsigned char *)(new_user->name), sdslen(new_user->name), channels, NULL);
                }
            }
            /* When the new channel list is NULL, it means the new user's channel list is a superset of the old user's
             * list. */
            if (!new_user || (channels && ACLShouldKillPubsubClient(c, channels))) {
                freeClientOrCloseLater(c, 0);
                continue;
            }
            c->user = new_user;
        }

        if (user_channels) raxFreeWithCallback(user_channels, listReleaseVoid);
        raxFreeWithCallback(old_users, ACLFreeUserVoid);
        sdsfree(errors);
        return NULL;
    } else {
        raxFreeWithCallback(Users, ACLFreeUserVoid);
        Users = old_users;
        errors =
            sdscat(errors, "WARNING: ACL errors detected, no change to the previously active ACL rules was performed");
        return errors;
    }
}

/* Generate a copy of the ACLs currently in memory in the specified filename.
 * Returns C_OK on success or C_ERR if there was an error during the I/O.
 * When C_ERR is returned a log is produced with hints about the issue. */
static int ACLSaveToFile(const char *filename) {
    sds acl = sdsempty();
    int fd = -1;
    sds tmpfilename = NULL;
    int retval = C_ERR;

    /* Let's generate an SDS string containing the new version of the
     * ACL file. */
    raxIterator ri;