|
@@ -0,0 +1,291 @@
|
|
|
+/*
|
|
|
+ * 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(¶ms);
|
|
|
+ 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;
|
|
|
+}
|