blob: a472dba764533fa8d22adb89fb334164c6f81abd [file] [log] [blame]
/*
* Copyright (c) 2017 The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/**
* DOC: wlan_hdd_fips.c
*
* WLAN Host Device Driver FIPS Certification Feature
*/
#include "wlan_hdd_main.h"
#include "wlan_hdd_fips.h"
#include "wlan_hdd_request_manager.h"
#include "qdf_mem.h"
#include "sme_api.h"
#define WLAN_WAIT_TIME_FIPS 5000
/**
* hdd_fips_context - hdd fips context
* @status: status of response. 0: no error, -ENOMEM: unable to allocate
* memory for the response payload
* @request: fips request
* @response: fips response
*/
struct hdd_fips_context {
int status;
struct fips_params request;
struct wmi_host_fips_event_param response;
};
/**
* hdd_fips_event_dup () - duplicate a fips event
* @dest: destination event
* @src: source event
*
* Make a "deep" duplicate of a FIPS event
*
* Return: 0 if the event was duplicated, otherwise an error
*/
static int hdd_fips_event_dup(struct wmi_host_fips_event_param *dest,
const struct wmi_host_fips_event_param *src)
{
*dest = *src;
if (dest->data_len) {
dest->data = qdf_mem_malloc(dest->data_len);
if (!dest->data) {
hdd_err("memory allocation failed");
return -ENOMEM;
}
qdf_mem_copy(dest->data, src->data, src->data_len);
} else {
/* make sure we don't have a rogue pointer */
dest->data = NULL;
}
return 0;
}
/**
* hdd_fips_cb () - fips response message handler
* @cookie: hdd request cookie
* @response: fips response parameters
*
* Return: none
*/
static void hdd_fips_cb(void *cookie,
struct wmi_host_fips_event_param *response)
{
struct hdd_request *request;
struct hdd_fips_context *context;
ENTER();
if (!response) {
hdd_err("response is NULL");
return;
}
request = hdd_request_get(cookie);
if (!request) {
hdd_debug("Obsolete request");
return;
}
hdd_debug("pdev_id %u, status %u, data_len %u",
response->pdev_id,
response->error_status,
response->data_len);
qdf_trace_hex_dump(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_DEBUG,
response->data, response->data_len);
context = hdd_request_priv(request);
if (response->error_status) {
context->status = -ETIMEDOUT;
} else {
context->status = hdd_fips_event_dup(&context->response,
response);
}
hdd_request_complete(request);
hdd_request_put(request);
EXIT();
}
static void hdd_fips_context_dealloc(void *priv)
{
struct hdd_fips_context *context = priv;
qdf_mem_free(context->response.data);
}
static int hdd_fips_validate_request(struct iw_fips_test_request *user_request,
uint32_t request_len)
{
uint32_t expected_data_len;
if (request_len < sizeof(*user_request)) {
hdd_debug("Request len %u is too small", request_len);
return -EINVAL;
}
if ((user_request->key_len != FIPS_KEY_LENGTH_128) &&
(user_request->key_len != FIPS_KEY_LENGTH_256)) {
hdd_debug("Invalid key len %u", user_request->key_len);
return -EINVAL;
}
expected_data_len = request_len - sizeof(*user_request);
if (expected_data_len != user_request->data_len) {
hdd_debug("Unexpected data_len %u for request_len %u",
user_request->data_len, request_len);
return -EINVAL;
}
if ((user_request->mode != FIPS_ENGINE_AES_CTR) &&
(user_request->mode != FIPS_ENGINE_AES_MIC)) {
hdd_debug("Invalid mode %u", user_request->mode);
return -EINVAL;
}
if ((user_request->operation != FIPS_ENCRYPT_CMD) &&
(user_request->operation != FIPS_DECRYPT_CMD)) {
hdd_debug("Invalid operation %u", user_request->operation);
return -EINVAL;
}
return 0;
}
static int __hdd_fips_test(struct net_device *dev,
struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
hdd_adapter_t *adapter;
hdd_context_t *hdd_ctx;
struct iw_fips_test_request *user_request;
struct iw_fips_test_response *user_response;
uint32_t request_len;
int ret;
QDF_STATUS qdf_status;
void *cookie;
struct hdd_request *request;
struct hdd_fips_context *context;
struct fips_params *fips_request;
struct wmi_host_fips_event_param *fips_response;
static const struct hdd_request_params params = {
.priv_size = sizeof(*context),
.timeout_ms = WLAN_WAIT_TIME_FIPS,
.dealloc = hdd_fips_context_dealloc,
};
ENTER_DEV(dev);
adapter = WLAN_HDD_GET_PRIV_PTR(dev);
hdd_ctx = WLAN_HDD_GET_CTX(adapter);
ret = wlan_hdd_validate_context(hdd_ctx);
if (ret)
return ret;
user_request = (struct iw_fips_test_request *)extra;
request_len = wrqu->data.length;
ret = hdd_fips_validate_request(user_request, request_len);
if (ret)
return ret;
request = hdd_request_alloc(&params);
if (!request) {
hdd_err("Request allocation failure");
return -ENOMEM;
}
context = hdd_request_priv(request);
fips_request = &context->request;
fips_request->key = &user_request->key[0];
fips_request->key_len = user_request->key_len;
fips_request->data = &user_request->data[0];
fips_request->data_len = user_request->data_len;
fips_request->mode = user_request->mode;
fips_request->op = user_request->operation;
fips_request->pdev_id = WMI_PDEV_ID_1ST;
cookie = hdd_request_cookie(request);
qdf_status = sme_fips_request(hdd_ctx->hHal, &context->request,
hdd_fips_cb, cookie);
if (!QDF_IS_STATUS_SUCCESS(qdf_status)) {
hdd_err("Unable to post fips message");
ret = -EINVAL;
goto cleanup;
}
ret = hdd_request_wait_for_response(request);
if (ret) {
hdd_err("Target response timed out");
goto cleanup;
}
ret = context->status;
if (ret) {
hdd_err("Target response processing failed");
goto cleanup;
}
fips_response = &context->response;
if (fips_response->data_len != fips_request->data_len) {
hdd_err("Data length mismatch, got %u, expected %u",
fips_response->data_len, fips_request->data_len);
ret = -EINVAL;
goto cleanup;
}
user_response = (struct iw_fips_test_response *)extra;
user_response->status = context->status;
if (user_response->status) {
user_response->data_len = 0;
} else {
user_response->data_len = fips_response->data_len;
qdf_mem_copy(user_response->data, fips_response->data,
fips_response->data_len);
}
/*
* By default wireless extensions private ioctls have either
* SET semantics (even numbered ioctls) or GET semantics (odd
* numbered ioctls). This is an even numbered ioctl so the SET
* semantics apply. This means the core kernel ioctl code took
* care of copying the request parameters from userspace to
* kernel space. However this ioctl also needs to return the
* response. Since the core kernel ioctl code doesn't support
* SET ioctls returning anything other than status, we have to
* explicitly copy the result to userspace.
*/
wrqu->data.length = sizeof(*user_response) + user_response->data_len;
if (copy_to_user(wrqu->data.pointer, user_response, wrqu->data.length))
ret = -EFAULT;
cleanup:
hdd_request_put(request);
EXIT();
return ret;
}
int hdd_fips_test(struct net_device *dev,
struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
int ret;
cds_ssr_protect(__func__);
ret = __hdd_fips_test(dev, info, wrqu, extra);
cds_ssr_unprotect(__func__);
return ret;
}