[pve-devel] [PATCH v2 cluster 1/2] pmxcfs: add verify_token IPCC request

Fabian Grünbichler f.gruenbichler at proxmox.com
Mon Nov 25 08:02:44 CET 2019


On November 23, 2019 5:21 pm, Thomas Lamprecht wrote:
> On 11/21/19 3:43 PM, Fabian Grünbichler wrote:
>> Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
>> ---
>>  data/src/cfs-ipc-ops.h |  2 ++
>>  data/src/server.c      | 58 ++++++++++++++++++++++++++++++++++++++++++
>>  data/src/status.c      |  1 +
>>  data/PVE/Cluster.pm    | 18 +++++++++++++
>>  4 files changed, 79 insertions(+)
>> 
>> diff --git a/data/src/cfs-ipc-ops.h b/data/src/cfs-ipc-ops.h
>> index e4026ad..9d788bc 100644
>> --- a/data/src/cfs-ipc-ops.h
>> +++ b/data/src/cfs-ipc-ops.h
>> @@ -41,4 +41,6 @@
>>  
>>  #define CFS_IPC_GET_GUEST_CONFIG_PROPERTY 11
>>  
>> +#define CFS_IPC_VERIFY_TOKEN 12
>> +
>>  #endif
>> diff --git a/data/src/server.c b/data/src/server.c
>> index 36acc1d..6d67f6b 100644
>> --- a/data/src/server.c
>> +++ b/data/src/server.c
>> @@ -89,6 +89,11 @@ typedef struct {
>>  	char property[];
>>  } cfs_guest_config_propery_get_request_header_t;
>>  
>> +typedef struct {
>> +	struct qb_ipc_request_header req_header;
>> +	char token[];
>> +} cfs_verify_token_request_header_t;
>> +
>>  struct s1_context {
>>  	int32_t client_pid;
>>  	uid_t uid;
>> @@ -343,6 +348,59 @@ static int32_t s1_msg_process_fn(
>>  
>>  			result = cfs_create_guest_conf_property_msg(outbuf, memdb, rh->property, rh->vmid);
>>  		}
>> +	} else if (request_id == CFS_IPC_VERIFY_TOKEN) {
>> +
>> +		cfs_verify_token_request_header_t *rh = (cfs_verify_token_request_header_t *) data;
>> +		int tokenlen = request_size - G_STRUCT_OFFSET(cfs_verify_token_request_header_t, token);
>> +
>> +		if (tokenlen <= 0) {
>> +			cfs_debug("tokenlen <= 0, %d", tokenlen);
>> +			result = -EINVAL;
>> +		} else if (memchr(rh->token, '\n', tokenlen) != NULL) {
>> +			cfs_debug("token contains newline");
>> +			result = -EINVAL;
>> +		} else if (rh->token[tokenlen-1] != '\0') {
>> +			cfs_debug("token not NULL-terminated");
>> +			result = -EINVAL;
>> +		} else if (strnlen(rh->token, tokenlen-1) != tokenlen - 1) {
>> +			cfs_debug("token contains NULL-byte");
>> +			result = -EINVAL;
>> +		} else {
>> +			cfs_debug("cfs_verify_token: basic validity checked, reading token.cfg");
>> +			gpointer tmp = NULL;
>> +			int bytes_read = memdb_read(memdb, "priv/token.cfg", &tmp);
> 
> priv/ makes this a bit namespaced already, but maybe "access-token.cfg"
> could be a bit more explaining
> 
>> +			size_t remaining = bytes_read > 0 ? bytes_read : 0;
>> +			if (tmp != NULL && remaining > 0) {
> 
> remaining >= tokenlen

ACK

> 
>> +				char *line = (char *) tmp;
>> +				char *next_line;
>> +				char *end = line + remaining;
>> +				*end = '\0';
> 
> should be:
> const char *const end = line + remaining;
> 
> You mustn't write at *end, that an out-of-bound access, and undefined
> behavior. Check my "_get_property_value" in status.c, had similar issues
> first, now it gets it right™.

I actually used that as a starting point ;) but you are of course 
correct, we can't ensure that the last line is 0-terminated this way.

> 
>> +
>> +				while (line != NULL) {
>> +					next_line = memchr(line, '\n', remaining);
>> +					if (next_line != NULL) {
>> +						*next_line = '\0';
> 
> unnecessary, just use (next_line or end) - line as boundaries and do a
> strncmp below[0]

but I actually want to ensure I compare both token and the full line, no 
matter which one is longer.

e.g., if the file contains (for whatever reason!)
someuser at pve!token asd213
someotheruser at pve!token asdas-asdas-dsadsa

and I compare against

someuser at pve!token asd213-dfsdasads-asdsdas-dasds

I don't want to compare across the newline! while it should never be 
able to produce a false match (we reject token values as needle that 
contain a newline), it seems like a good safeguard to ensure we only 
match one full line at a time..

> 
>> +					}
>> +					/* we ensure above that both are properly terminated */
> 
> you ain't if next_line was NULL, i.e., no \n at EOF, at least if you
> do not do your out-of-bound write to *end = 0 anymore ;-)
> 
>> +					if (strcmp(line, rh->token) == 0) {
> 
> [0] ...here
> 
> effectively I'd go with:
> 
> int bytes_read = memdb_read(memdb, "priv/token.cfg", &tmp);
> size_t remaining = bytes_read > 0 ? bytes_read : 0;
> if (tmp != NULL && remaining >= tokenlen) {
>     char *line = (char *) tmp;
>     char *next_newline;
>     char *const end = line + remaining;
> 
>     while (line != NULL) {
>         next_newline = memchr(line, '\n', remaining);
>         if (next_newline != NULL) {
>             *next_newline = '\0';

so in practice I'd add another check here that the current line 
(next_newline - line) is as long as rh->token, and if it is not, proceed 
directly to the next line. this might also speed it up, since we now 
only do the full string/memory comparison against stored values where 
the full tokenid has the same length as the one we are looking for.

if next_newline == NULL, then we must be in the last loop, and remaining 
should be exactly tokenlen, so we could check that in an else branch and 
bail if that check fails.

>         }
> 

>         if (memcmp(line, rh->token, tokenlen) == 0) {
>             result = 0; // found
>             break;
>         }
> 
>         line = next_newline;
>         if (line != NULL) {
>             line += 1;
>             remaining = end - line;
>             if (remaining < tokenlen) {
>                 result = -ENOENT;
>                 break;
>             }
>         }
>     }
>     if (line == NULL) {
>         result = -ENOENT;
>     }
>     g_free(tmp);
> } else {
>     cfs_debug("token: token.cfg does not exist - ENOENT");
>     result = -ENOENT;
> }
> 
> here, the actually search part could be moved out to a is_line_in_conf
> function, as it's completely agnostic to tokens itself..

sounds like a good idea - but where to put it? actually create the msg 
and put most of the code in status.c, like for the property getter? or 
keep the token stuff inline, and just move the line-search to 
cfs-utils.c?

> 
> 
>> +						result = 0;
>> +						break;
>> +					}
>> +					line = next_line;
>> +					if (line != NULL) {
>> +						line += 1;
>> +						remaining = end - line;
>> +					}
>> +				}
>> +				if (line == NULL) {
>> +					result = -ENOENT;
>> +				}
>> +				g_free(tmp);
>> +			} else {
>> +				cfs_debug("token: token.cfg does not exist - ENOENT");
>> +				result = -ENOENT;
>> +			}
>> +		}
>>  	}
>>  
>>  	cfs_debug("process result %d", result);
>> diff --git a/data/src/status.c b/data/src/status.c
>> index 62eaa76..453fcaf 100644
>> --- a/data/src/status.c
>> +++ b/data/src/status.c
>> @@ -96,6 +96,7 @@ static memdb_change_t memdb_change_array[] = {
>>  	{ .path = "ceph.conf" },
>>  	{ .path = "sdn.cfg" },
>>  	{ .path = "sdn.cfg.new" },
>> +	{ .path = "priv/token.cfg" },
>>  };
>>  
>>  static GMutex mutex;
>> diff --git a/data/PVE/Cluster.pm b/data/PVE/Cluster.pm
>> index 2057162..e888ae8 100644
>> --- a/data/PVE/Cluster.pm
>> +++ b/data/PVE/Cluster.pm
>> @@ -182,6 +182,18 @@ my $ipcc_get_cluster_log = sub {
>>      return &$ipcc_send_rec(CFS_IPC_GET_CLUSTER_LOG, $bindata);
>>  };
>>  
>> +my $ipcc_verify_token = sub {
>> +    my ($full_token) = @_;
>> +
>> +    my $bindata = pack "Z*", $full_token;
>> +    my $res = PVE::IPCC::ipcc_send_rec(CFS_IPC_VERIFY_TOKEN, $bindata);
>> +
>> +    return 1 if $! == 0;
>> +    return 0 if $! == ENOENT;
>> +
>> +    die "$!\n";
>> +};
>> +
>>  my $ccache = {};
>>  
>>  sub cfs_update {
>> @@ -442,6 +454,12 @@ sub get_cluster_log {
>>      return &$ipcc_get_cluster_log($user, $max);
>>  }
>>  
>> +sub verify_token {
>> +    my ($userid, $token) = @_;
>> +
>> +    return &$ipcc_verify_token("$userid $token");
>> +}
>> +
>>  my $file_info = {};
>>  
>>  sub cfs_register_file {
>> 
> 
> 
> 




More information about the pve-devel mailing list