| /* |
| * Copyright (c) 2017-2018 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; |
| |
| hdd_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); |
| hdd_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) |
| { |
| struct hdd_adapter *adapter; |
| struct hdd_context *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, |
| }; |
| |
| hdd_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); |
| |
| hdd_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; |
| } |