mirror of
https://github.com/clearlinux/swupd-client.git
synced 2026-09-07 22:21:32 +00:00
302 lines
7.8 KiB
C
302 lines
7.8 KiB
C
/*
|
|
* Software Updater - client side
|
|
*
|
|
* Copyright © 2012-2016 Intel Corporation.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, version 2 or later of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors:
|
|
* Arjan van de Ven <arjan@linux.intel.com>
|
|
* Tim Pepper <timothy.c.pepper@linux.intel.com>
|
|
*
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <openssl/hmac.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include "swupd.h"
|
|
#include "swupd_build_variant.h"
|
|
#include "xattrs.h"
|
|
|
|
void hash_assign(const char *src, char *dst)
|
|
{
|
|
memcpy(dst, src, SWUPD_HASH_LEN - 1);
|
|
dst[SWUPD_HASH_LEN - 1] = '\0';
|
|
}
|
|
|
|
bool hash_equal(const char *hash1, const char *hash2)
|
|
{
|
|
if (bcmp(hash1, hash2, SWUPD_HASH_LEN - 1) == 0) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool hash_is_zeros(char *hash)
|
|
{
|
|
return hash_equal("0000000000000000000000000000000000000000000000000000000000000000", hash);
|
|
}
|
|
|
|
static void hash_set_zeros(char *hash)
|
|
{
|
|
hash_assign("0000000000000000000000000000000000000000000000000000000000000000", hash);
|
|
}
|
|
|
|
static void hash_set_ones(char *hash)
|
|
{
|
|
hash_assign("1111111111111111111111111111111111111111111111111111111111111111", hash);
|
|
}
|
|
|
|
static void hmac_sha256_for_data(char *hash,
|
|
const unsigned char *key, size_t key_len,
|
|
const unsigned char *data, size_t data_len)
|
|
{
|
|
unsigned char digest[EVP_MAX_MD_SIZE];
|
|
unsigned int digest_len = 0;
|
|
char *digest_str;
|
|
size_t digest_str_len;
|
|
unsigned int i;
|
|
|
|
if (data == NULL) {
|
|
hash_set_zeros(hash);
|
|
return;
|
|
}
|
|
|
|
if (HMAC(EVP_sha256(), (const void *)key, key_len, data, data_len, digest, &digest_len) == NULL) {
|
|
hash_set_zeros(hash);
|
|
return;
|
|
}
|
|
|
|
digest_str_len = MAX((digest_len * 2) + 1, SWUPD_HASH_LEN);
|
|
digest_str = calloc(digest_str_len, sizeof(char));
|
|
ON_NULL_ABORT(digest_str);
|
|
|
|
for (i = 0; i < digest_len; i++) {
|
|
snprintf(&digest_str[i * 2], digest_str_len, "%02x", (unsigned int)digest[i]);
|
|
}
|
|
|
|
hash_assign(digest_str, hash);
|
|
free_and_clear_pointer(&digest_str);
|
|
}
|
|
|
|
static void hmac_sha256_for_string(char *hash,
|
|
const unsigned char *key, size_t key_len,
|
|
const char *str)
|
|
{
|
|
if (str == NULL) {
|
|
hash_set_zeros(hash);
|
|
return;
|
|
}
|
|
|
|
hmac_sha256_for_data(hash, key, key_len, (const unsigned char *)str, string_len(str));
|
|
}
|
|
|
|
static void hmac_compute_key(const char *filename,
|
|
const struct update_stat *updt_stat,
|
|
char *key, size_t *key_len, bool use_xattrs)
|
|
{
|
|
char *xattrs_blob = (void *)0xdeadcafe;
|
|
size_t xattrs_blob_len = 0;
|
|
|
|
if (use_xattrs) {
|
|
xattrs_get_blob(filename, &xattrs_blob, &xattrs_blob_len);
|
|
}
|
|
|
|
hmac_sha256_for_data(key, (const unsigned char *)updt_stat,
|
|
sizeof(struct update_stat),
|
|
(const unsigned char *)xattrs_blob,
|
|
xattrs_blob_len);
|
|
|
|
if (hash_is_zeros(key)) {
|
|
*key_len = 0;
|
|
} else {
|
|
*key_len = SWUPD_HASH_LEN - 1;
|
|
}
|
|
|
|
if (xattrs_blob_len != 0) {
|
|
free_and_clear_pointer(&xattrs_blob);
|
|
}
|
|
}
|
|
|
|
/* provide a wrapper for compute_hash() because we want a cheap-out option in
|
|
* case we are looking for missing files only:
|
|
* zeros hash: file missing
|
|
* ones hash: file present */
|
|
int compute_hash_lazy(struct file *file, char *filename)
|
|
{
|
|
struct stat sb;
|
|
if (lstat(filename, &sb) == 0) {
|
|
hash_set_ones(file->hash);
|
|
} else {
|
|
hash_set_zeros(file->hash);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* this function MUST be kept in sync with the server
|
|
* return is -1 if there was an error. If the file does not exist,
|
|
* a "0000000..." hash is returned as is our convention in the manifest
|
|
* for deleted files. Otherwise file->hash is set to a non-zero hash. */
|
|
/* TODO: how should we properly handle compute_hash() failures? */
|
|
enum swupd_code compute_hash(struct file *file, char *filename)
|
|
{
|
|
int ret;
|
|
char key[SWUPD_HASH_LEN];
|
|
size_t key_len;
|
|
unsigned char *blob;
|
|
FILE *fl;
|
|
|
|
if (file->is_deleted) {
|
|
hash_set_zeros(file->hash);
|
|
return SWUPD_OK;
|
|
}
|
|
|
|
hash_set_zeros(key);
|
|
|
|
if (file->is_link) {
|
|
char link[PATH_MAX];
|
|
memset(link, 0, PATH_MAX);
|
|
|
|
ret = readlink(filename, link, PATH_MAX - 1);
|
|
|
|
if (ret >= 0) {
|
|
link[ret] = '\0';
|
|
hmac_compute_key(filename, &file->stat, key, &key_len, file->use_xattrs);
|
|
hmac_sha256_for_string(file->hash,
|
|
(const unsigned char *)key,
|
|
key_len,
|
|
link);
|
|
return SWUPD_OK;
|
|
} else {
|
|
return SWUPD_COMPUTE_HASH_ERROR;
|
|
}
|
|
}
|
|
|
|
if (file->is_dir) {
|
|
hmac_compute_key(filename, &file->stat, key, &key_len, file->use_xattrs);
|
|
hmac_sha256_for_string(file->hash,
|
|
(const unsigned char *)key,
|
|
key_len,
|
|
SWUPD_HASH_DIRNAME); //Make independent of dirname
|
|
return SWUPD_OK;
|
|
}
|
|
|
|
/* if we get here, this is a regular file */
|
|
fl = fopen(filename, "r");
|
|
if (!fl) {
|
|
return SWUPD_COMPUTE_HASH_ERROR;
|
|
}
|
|
blob = mmap(NULL, file->stat.st_size, PROT_READ, MAP_PRIVATE, fileno(fl), 0);
|
|
if (blob == MAP_FAILED && file->stat.st_size != 0) {
|
|
fclose(fl);
|
|
return SWUPD_COMPUTE_HASH_ERROR;
|
|
}
|
|
|
|
hmac_compute_key(filename, &file->stat, key, &key_len, file->use_xattrs);
|
|
hmac_sha256_for_data(file->hash,
|
|
(const unsigned char *)key,
|
|
key_len,
|
|
blob,
|
|
file->stat.st_size);
|
|
munmap(blob, file->stat.st_size);
|
|
fclose(fl);
|
|
return SWUPD_OK;
|
|
}
|
|
|
|
bool verify_file(struct file *file, char *filename)
|
|
{
|
|
struct file local = { 0 };
|
|
|
|
local.filename = file->filename;
|
|
/*
|
|
* xattrs are currently not supported for manifest files.
|
|
* They are data files produced by the swupd-server and
|
|
* therefore do not have any of the xattrs normally
|
|
* set for the actual system files (like security.ima
|
|
* when using IMA or security.SMACK64 when using Smack).
|
|
*/
|
|
local.use_xattrs = !file->is_manifest;
|
|
|
|
populate_file_struct(&local, filename);
|
|
if (compute_hash(&local, filename) != 0) {
|
|
return false;
|
|
}
|
|
|
|
/* Check if manifest hash matches local file hash */
|
|
return hash_equal(file->hash, local.hash);
|
|
}
|
|
|
|
bool verify_file_lazy(char *filename)
|
|
{
|
|
struct file local = { 0 };
|
|
|
|
if (compute_hash_lazy(&local, filename) != 0) {
|
|
return false;
|
|
}
|
|
|
|
return !hash_is_zeros(local.hash);
|
|
}
|
|
|
|
/* Compares the hash for BUNDLE with that listed in the Manifest.MoM. If the
|
|
* hash check fails, we should assume the bundle manifest is incorrect and
|
|
* discard it. A retry should then force redownloading of the bundle manifest.
|
|
*/
|
|
int verify_bundle_hash(struct manifest *mom, struct file *bundle)
|
|
{
|
|
struct file *current;
|
|
char *local = NULL, *cached;
|
|
int ret = 0;
|
|
|
|
current = list_search(list_head(mom->manifests), bundle, cmp_file_filename);
|
|
if (!current) {
|
|
return -1;
|
|
}
|
|
|
|
string_or_die(&cached, "%s/%i/Manifest.%s.%s", globals.state_dir,
|
|
current->last_change, current->filename, bundle->hash);
|
|
|
|
string_or_die(&local, "%s/%i/Manifest.%s", globals.state_dir,
|
|
current->last_change, current->filename);
|
|
|
|
/* *NOTE* If the file is changed after being hardlinked, the hash will be different,
|
|
* but the inode will not change making swupd skip hash check thinking they still match */
|
|
if (sys_file_is_hardlink(local, cached)) {
|
|
goto out;
|
|
}
|
|
|
|
unlink(cached);
|
|
if (!verify_file(bundle, local)) {
|
|
warn("hash check failed for Manifest.%s for version %i. Deleting it\n", current->filename, mom->version);
|
|
unlink(local);
|
|
ret = -1;
|
|
} else {
|
|
if (link(local, cached) < 0) {
|
|
debug("Failed to link cache %s manifest", local);
|
|
}
|
|
}
|
|
|
|
out:
|
|
free_and_clear_pointer(&cached);
|
|
free_and_clear_pointer(&local);
|
|
return ret;
|
|
}
|