cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

vl_alias.c (9434B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/* AFS cell alias detection
      3 *
      4 * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
      5 * Written by David Howells (dhowells@redhat.com)
      6 */
      7
      8#include <linux/slab.h>
      9#include <linux/sched.h>
     10#include <linux/namei.h>
     11#include <keys/rxrpc-type.h>
     12#include "internal.h"
     13
     14/*
     15 * Sample a volume.
     16 */
     17static struct afs_volume *afs_sample_volume(struct afs_cell *cell, struct key *key,
     18					    const char *name, unsigned int namelen)
     19{
     20	struct afs_volume *volume;
     21	struct afs_fs_context fc = {
     22		.type		= 0, /* Explicitly leave it to the VLDB */
     23		.volnamesz	= namelen,
     24		.volname	= name,
     25		.net		= cell->net,
     26		.cell		= cell,
     27		.key		= key, /* This might need to be something */
     28	};
     29
     30	volume = afs_create_volume(&fc);
     31	_leave(" = %p", volume);
     32	return volume;
     33}
     34
     35/*
     36 * Compare two addresses.
     37 */
     38static int afs_compare_addrs(const struct sockaddr_rxrpc *srx_a,
     39			     const struct sockaddr_rxrpc *srx_b)
     40{
     41	short port_a, port_b;
     42	int addr_a, addr_b, diff;
     43
     44	diff = (short)srx_a->transport_type - (short)srx_b->transport_type;
     45	if (diff)
     46		goto out;
     47
     48	switch (srx_a->transport_type) {
     49	case AF_INET: {
     50		const struct sockaddr_in *a = &srx_a->transport.sin;
     51		const struct sockaddr_in *b = &srx_b->transport.sin;
     52		addr_a = ntohl(a->sin_addr.s_addr);
     53		addr_b = ntohl(b->sin_addr.s_addr);
     54		diff = addr_a - addr_b;
     55		if (diff == 0) {
     56			port_a = ntohs(a->sin_port);
     57			port_b = ntohs(b->sin_port);
     58			diff = port_a - port_b;
     59		}
     60		break;
     61	}
     62
     63	case AF_INET6: {
     64		const struct sockaddr_in6 *a = &srx_a->transport.sin6;
     65		const struct sockaddr_in6 *b = &srx_b->transport.sin6;
     66		diff = memcmp(&a->sin6_addr, &b->sin6_addr, 16);
     67		if (diff == 0) {
     68			port_a = ntohs(a->sin6_port);
     69			port_b = ntohs(b->sin6_port);
     70			diff = port_a - port_b;
     71		}
     72		break;
     73	}
     74
     75	default:
     76		WARN_ON(1);
     77		diff = 1;
     78	}
     79
     80out:
     81	return diff;
     82}
     83
     84/*
     85 * Compare the address lists of a pair of fileservers.
     86 */
     87static int afs_compare_fs_alists(const struct afs_server *server_a,
     88				 const struct afs_server *server_b)
     89{
     90	const struct afs_addr_list *la, *lb;
     91	int a = 0, b = 0, addr_matches = 0;
     92
     93	la = rcu_dereference(server_a->addresses);
     94	lb = rcu_dereference(server_b->addresses);
     95
     96	while (a < la->nr_addrs && b < lb->nr_addrs) {
     97		const struct sockaddr_rxrpc *srx_a = &la->addrs[a];
     98		const struct sockaddr_rxrpc *srx_b = &lb->addrs[b];
     99		int diff = afs_compare_addrs(srx_a, srx_b);
    100
    101		if (diff < 0) {
    102			a++;
    103		} else if (diff > 0) {
    104			b++;
    105		} else {
    106			addr_matches++;
    107			a++;
    108			b++;
    109		}
    110	}
    111
    112	return addr_matches;
    113}
    114
    115/*
    116 * Compare the fileserver lists of two volumes.  The server lists are sorted in
    117 * order of ascending UUID.
    118 */
    119static int afs_compare_volume_slists(const struct afs_volume *vol_a,
    120				     const struct afs_volume *vol_b)
    121{
    122	const struct afs_server_list *la, *lb;
    123	int i, a = 0, b = 0, uuid_matches = 0, addr_matches = 0;
    124
    125	la = rcu_dereference(vol_a->servers);
    126	lb = rcu_dereference(vol_b->servers);
    127
    128	for (i = 0; i < AFS_MAXTYPES; i++)
    129		if (la->vids[i] != lb->vids[i])
    130			return 0;
    131
    132	while (a < la->nr_servers && b < lb->nr_servers) {
    133		const struct afs_server *server_a = la->servers[a].server;
    134		const struct afs_server *server_b = lb->servers[b].server;
    135		int diff = memcmp(&server_a->uuid, &server_b->uuid, sizeof(uuid_t));
    136
    137		if (diff < 0) {
    138			a++;
    139		} else if (diff > 0) {
    140			b++;
    141		} else {
    142			uuid_matches++;
    143			addr_matches += afs_compare_fs_alists(server_a, server_b);
    144			a++;
    145			b++;
    146		}
    147	}
    148
    149	_leave(" = %d [um %d]", addr_matches, uuid_matches);
    150	return addr_matches;
    151}
    152
    153/*
    154 * Compare root.cell volumes.
    155 */
    156static int afs_compare_cell_roots(struct afs_cell *cell)
    157{
    158	struct afs_cell *p;
    159
    160	_enter("");
    161
    162	rcu_read_lock();
    163
    164	hlist_for_each_entry_rcu(p, &cell->net->proc_cells, proc_link) {
    165		if (p == cell || p->alias_of)
    166			continue;
    167		if (!p->root_volume)
    168			continue; /* Ignore cells that don't have a root.cell volume. */
    169
    170		if (afs_compare_volume_slists(cell->root_volume, p->root_volume) != 0)
    171			goto is_alias;
    172	}
    173
    174	rcu_read_unlock();
    175	_leave(" = 0");
    176	return 0;
    177
    178is_alias:
    179	rcu_read_unlock();
    180	cell->alias_of = afs_use_cell(p, afs_cell_trace_use_alias);
    181	return 1;
    182}
    183
    184/*
    185 * Query the new cell for a volume from a cell we're already using.
    186 */
    187static int afs_query_for_alias_one(struct afs_cell *cell, struct key *key,
    188				   struct afs_cell *p)
    189{
    190	struct afs_volume *volume, *pvol = NULL;
    191	int ret;
    192
    193	/* Arbitrarily pick a volume from the list. */
    194	read_seqlock_excl(&p->volume_lock);
    195	if (!RB_EMPTY_ROOT(&p->volumes))
    196		pvol = afs_get_volume(rb_entry(p->volumes.rb_node,
    197					       struct afs_volume, cell_node),
    198				      afs_volume_trace_get_query_alias);
    199	read_sequnlock_excl(&p->volume_lock);
    200	if (!pvol)
    201		return 0;
    202
    203	_enter("%s:%s", cell->name, pvol->name);
    204
    205	/* And see if it's in the new cell. */
    206	volume = afs_sample_volume(cell, key, pvol->name, pvol->name_len);
    207	if (IS_ERR(volume)) {
    208		afs_put_volume(cell->net, pvol, afs_volume_trace_put_query_alias);
    209		if (PTR_ERR(volume) != -ENOMEDIUM)
    210			return PTR_ERR(volume);
    211		/* That volume is not in the new cell, so not an alias */
    212		return 0;
    213	}
    214
    215	/* The new cell has a like-named volume also - compare volume ID,
    216	 * server and address lists.
    217	 */
    218	ret = 0;
    219	if (pvol->vid == volume->vid) {
    220		rcu_read_lock();
    221		if (afs_compare_volume_slists(volume, pvol))
    222			ret = 1;
    223		rcu_read_unlock();
    224	}
    225
    226	afs_put_volume(cell->net, volume, afs_volume_trace_put_query_alias);
    227	afs_put_volume(cell->net, pvol, afs_volume_trace_put_query_alias);
    228	return ret;
    229}
    230
    231/*
    232 * Query the new cell for volumes we know exist in cells we're already using.
    233 */
    234static int afs_query_for_alias(struct afs_cell *cell, struct key *key)
    235{
    236	struct afs_cell *p;
    237
    238	_enter("%s", cell->name);
    239
    240	if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0)
    241		return -ERESTARTSYS;
    242
    243	hlist_for_each_entry(p, &cell->net->proc_cells, proc_link) {
    244		if (p == cell || p->alias_of)
    245			continue;
    246		if (RB_EMPTY_ROOT(&p->volumes))
    247			continue;
    248		if (p->root_volume)
    249			continue; /* Ignore cells that have a root.cell volume. */
    250		afs_use_cell(p, afs_cell_trace_use_check_alias);
    251		mutex_unlock(&cell->net->proc_cells_lock);
    252
    253		if (afs_query_for_alias_one(cell, key, p) != 0)
    254			goto is_alias;
    255
    256		if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0) {
    257			afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
    258			return -ERESTARTSYS;
    259		}
    260
    261		afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
    262	}
    263
    264	mutex_unlock(&cell->net->proc_cells_lock);
    265	_leave(" = 0");
    266	return 0;
    267
    268is_alias:
    269	cell->alias_of = p; /* Transfer our ref */
    270	return 1;
    271}
    272
    273/*
    274 * Look up a VLDB record for a volume.
    275 */
    276static char *afs_vl_get_cell_name(struct afs_cell *cell, struct key *key)
    277{
    278	struct afs_vl_cursor vc;
    279	char *cell_name = ERR_PTR(-EDESTADDRREQ);
    280	bool skipped = false, not_skipped = false;
    281	int ret;
    282
    283	if (!afs_begin_vlserver_operation(&vc, cell, key))
    284		return ERR_PTR(-ERESTARTSYS);
    285
    286	while (afs_select_vlserver(&vc)) {
    287		if (!test_bit(AFS_VLSERVER_FL_IS_YFS, &vc.server->flags)) {
    288			vc.ac.error = -EOPNOTSUPP;
    289			skipped = true;
    290			continue;
    291		}
    292		not_skipped = true;
    293		cell_name = afs_yfsvl_get_cell_name(&vc);
    294	}
    295
    296	ret = afs_end_vlserver_operation(&vc);
    297	if (skipped && !not_skipped)
    298		ret = -EOPNOTSUPP;
    299	return ret < 0 ? ERR_PTR(ret) : cell_name;
    300}
    301
    302static int yfs_check_canonical_cell_name(struct afs_cell *cell, struct key *key)
    303{
    304	struct afs_cell *master;
    305	char *cell_name;
    306
    307	cell_name = afs_vl_get_cell_name(cell, key);
    308	if (IS_ERR(cell_name))
    309		return PTR_ERR(cell_name);
    310
    311	if (strcmp(cell_name, cell->name) == 0) {
    312		kfree(cell_name);
    313		return 0;
    314	}
    315
    316	master = afs_lookup_cell(cell->net, cell_name, strlen(cell_name),
    317				 NULL, false);
    318	kfree(cell_name);
    319	if (IS_ERR(master))
    320		return PTR_ERR(master);
    321
    322	cell->alias_of = master; /* Transfer our ref */
    323	return 1;
    324}
    325
    326static int afs_do_cell_detect_alias(struct afs_cell *cell, struct key *key)
    327{
    328	struct afs_volume *root_volume;
    329	int ret;
    330
    331	_enter("%s", cell->name);
    332
    333	ret = yfs_check_canonical_cell_name(cell, key);
    334	if (ret != -EOPNOTSUPP)
    335		return ret;
    336
    337	/* Try and get the root.cell volume for comparison with other cells */
    338	root_volume = afs_sample_volume(cell, key, "root.cell", 9);
    339	if (!IS_ERR(root_volume)) {
    340		cell->root_volume = root_volume;
    341		return afs_compare_cell_roots(cell);
    342	}
    343
    344	if (PTR_ERR(root_volume) != -ENOMEDIUM)
    345		return PTR_ERR(root_volume);
    346
    347	/* Okay, this cell doesn't have an root.cell volume.  We need to
    348	 * locate some other random volume and use that to check.
    349	 */
    350	return afs_query_for_alias(cell, key);
    351}
    352
    353/*
    354 * Check to see if a new cell is an alias of a cell we already have.  At this
    355 * point we have the cell's volume server list.
    356 *
    357 * Returns 0 if we didn't detect an alias, 1 if we found an alias and an error
    358 * if we had problems gathering the data required.  In the case the we did
    359 * detect an alias, cell->alias_of is set to point to the assumed master.
    360 */
    361int afs_cell_detect_alias(struct afs_cell *cell, struct key *key)
    362{
    363	struct afs_net *net = cell->net;
    364	int ret;
    365
    366	if (mutex_lock_interruptible(&net->cells_alias_lock) < 0)
    367		return -ERESTARTSYS;
    368
    369	if (test_bit(AFS_CELL_FL_CHECK_ALIAS, &cell->flags)) {
    370		ret = afs_do_cell_detect_alias(cell, key);
    371		if (ret >= 0)
    372			clear_bit_unlock(AFS_CELL_FL_CHECK_ALIAS, &cell->flags);
    373	} else {
    374		ret = cell->alias_of ? 1 : 0;
    375	}
    376
    377	mutex_unlock(&net->cells_alias_lock);
    378
    379	if (ret == 1)
    380		pr_notice("kAFS: Cell %s is an alias of %s\n",
    381			  cell->name, cell->alias_of->name);
    382	return ret;
    383}