linux-stable/drivers/soc/qcom/pdr_interface.c
Len Baker 26bc7a6a0b soc: qcom: pdr: Prefer strscpy over strcpy
strcpy() performs no bounds checking on the destination buffer. This
could result in linear overflows beyond the end of the buffer, leading
to all kinds of misbehaviors. The safe replacement is strscpy().

This is a previous step in the path to remove the strcpy() function
entirely from the kernel.

Signed-off-by: Len Baker <len.baker@gmx.com>
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
Link: https://lore.kernel.org/r/20210808125012.4715-2-len.baker@gmx.com
2021-09-21 17:41:49 -05:00

756 lines
18 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020 The Linux Foundation. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/workqueue.h>
#include "pdr_internal.h"
struct pdr_service {
char service_name[SERVREG_NAME_LENGTH + 1];
char service_path[SERVREG_NAME_LENGTH + 1];
struct sockaddr_qrtr addr;
unsigned int instance;
unsigned int service;
u8 service_data_valid;
u32 service_data;
int state;
bool need_notifier_register;
bool need_notifier_remove;
bool need_locator_lookup;
bool service_connected;
struct list_head node;
};
struct pdr_handle {
struct qmi_handle locator_hdl;
struct qmi_handle notifier_hdl;
struct sockaddr_qrtr locator_addr;
struct list_head lookups;
struct list_head indack_list;
/* control access to pdr lookup/indack lists */
struct mutex list_lock;
/* serialize pd status invocation */
struct mutex status_lock;
/* control access to the locator state */
struct mutex lock;
bool locator_init_complete;
struct work_struct locator_work;
struct work_struct notifier_work;
struct work_struct indack_work;
struct workqueue_struct *notifier_wq;
struct workqueue_struct *indack_wq;
void (*status)(int state, char *service_path, void *priv);
void *priv;
};
struct pdr_list_node {
enum servreg_service_state curr_state;
u16 transaction_id;
struct pdr_service *pds;
struct list_head node;
};
static int pdr_locator_new_server(struct qmi_handle *qmi,
struct qmi_service *svc)
{
struct pdr_handle *pdr = container_of(qmi, struct pdr_handle,
locator_hdl);
struct pdr_service *pds;
/* Create a local client port for QMI communication */
pdr->locator_addr.sq_family = AF_QIPCRTR;
pdr->locator_addr.sq_node = svc->node;
pdr->locator_addr.sq_port = svc->port;
mutex_lock(&pdr->lock);
pdr->locator_init_complete = true;
mutex_unlock(&pdr->lock);
/* Service pending lookup requests */
mutex_lock(&pdr->list_lock);
list_for_each_entry(pds, &pdr->lookups, node) {
if (pds->need_locator_lookup)
schedule_work(&pdr->locator_work);
}
mutex_unlock(&pdr->list_lock);
return 0;
}
static void pdr_locator_del_server(struct qmi_handle *qmi,
struct qmi_service *svc)
{
struct pdr_handle *pdr = container_of(qmi, struct pdr_handle,
locator_hdl);
mutex_lock(&pdr->lock);
pdr->locator_init_complete = false;
mutex_unlock(&pdr->lock);
pdr->locator_addr.sq_node = 0;
pdr->locator_addr.sq_port = 0;
}
static const struct qmi_ops pdr_locator_ops = {
.new_server = pdr_locator_new_server,
.del_server = pdr_locator_del_server,
};
static int pdr_register_listener(struct pdr_handle *pdr,
struct pdr_service *pds,
bool enable)
{
struct servreg_register_listener_resp resp;
struct servreg_register_listener_req req;
struct qmi_txn txn;
int ret;
ret = qmi_txn_init(&pdr->notifier_hdl, &txn,
servreg_register_listener_resp_ei,
&resp);
if (ret < 0)
return ret;
req.enable = enable;
strscpy(req.service_path, pds->service_path, sizeof(req.service_path));
ret = qmi_send_request(&pdr->notifier_hdl, &pds->addr,
&txn, SERVREG_REGISTER_LISTENER_REQ,
SERVREG_REGISTER_LISTENER_REQ_LEN,
servreg_register_listener_req_ei,
&req);
if (ret < 0) {
qmi_txn_cancel(&txn);
return ret;
}
ret = qmi_txn_wait(&txn, 5 * HZ);
if (ret < 0) {
pr_err("PDR: %s register listener txn wait failed: %d\n",
pds->service_path, ret);
return ret;
}
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
pr_err("PDR: %s register listener failed: 0x%x\n",
pds->service_path, resp.resp.error);
return -EREMOTEIO;
}
pds->state = resp.curr_state;
return 0;
}
static void pdr_notifier_work(struct work_struct *work)
{
struct pdr_handle *pdr = container_of(work, struct pdr_handle,
notifier_work);
struct pdr_service *pds;
int ret;
mutex_lock(&pdr->list_lock);
list_for_each_entry(pds, &pdr->lookups, node) {
if (pds->service_connected) {
if (!pds->need_notifier_register)
continue;
pds->need_notifier_register = false;
ret = pdr_register_listener(pdr, pds, true);
if (ret < 0)
pds->state = SERVREG_SERVICE_STATE_DOWN;
} else {
if (!pds->need_notifier_remove)
continue;
pds->need_notifier_remove = false;
pds->state = SERVREG_SERVICE_STATE_DOWN;
}
mutex_lock(&pdr->status_lock);
pdr->status(pds->state, pds->service_path, pdr->priv);
mutex_unlock(&pdr->status_lock);
}
mutex_unlock(&pdr->list_lock);
}
static int pdr_notifier_new_server(struct qmi_handle *qmi,
struct qmi_service *svc)
{
struct pdr_handle *pdr = container_of(qmi, struct pdr_handle,
notifier_hdl);
struct pdr_service *pds;
mutex_lock(&pdr->list_lock);
list_for_each_entry(pds, &pdr->lookups, node) {
if (pds->service == svc->service &&
pds->instance == svc->instance) {
pds->service_connected = true;
pds->need_notifier_register = true;
pds->addr.sq_family = AF_QIPCRTR;
pds->addr.sq_node = svc->node;
pds->addr.sq_port = svc->port;
queue_work(pdr->notifier_wq, &pdr->notifier_work);
}
}
mutex_unlock(&pdr->list_lock);
return 0;
}
static void pdr_notifier_del_server(struct qmi_handle *qmi,
struct qmi_service *svc)
{
struct pdr_handle *pdr = container_of(qmi, struct pdr_handle,
notifier_hdl);
struct pdr_service *pds;
mutex_lock(&pdr->list_lock);
list_for_each_entry(pds, &pdr->lookups, node) {
if (pds->service == svc->service &&
pds->instance == svc->instance) {
pds->service_connected = false;
pds->need_notifier_remove = true;
pds->addr.sq_node = 0;
pds->addr.sq_port = 0;
queue_work(pdr->notifier_wq, &pdr->notifier_work);
}
}
mutex_unlock(&pdr->list_lock);
}
static const struct qmi_ops pdr_notifier_ops = {
.new_server = pdr_notifier_new_server,
.del_server = pdr_notifier_del_server,
};
static int pdr_send_indack_msg(struct pdr_handle *pdr, struct pdr_service *pds,
u16 tid)
{
struct servreg_set_ack_resp resp;
struct servreg_set_ack_req req;
struct qmi_txn txn;
int ret;
ret = qmi_txn_init(&pdr->notifier_hdl, &txn, servreg_set_ack_resp_ei,
&resp);
if (ret < 0)
return ret;
req.transaction_id = tid;
strscpy(req.service_path, pds->service_path, sizeof(req.service_path));
ret = qmi_send_request(&pdr->notifier_hdl, &pds->addr,
&txn, SERVREG_SET_ACK_REQ,
SERVREG_SET_ACK_REQ_LEN,
servreg_set_ack_req_ei,
&req);
/* Skip waiting for response */
qmi_txn_cancel(&txn);
return ret;
}
static void pdr_indack_work(struct work_struct *work)
{
struct pdr_handle *pdr = container_of(work, struct pdr_handle,
indack_work);
struct pdr_list_node *ind, *tmp;
struct pdr_service *pds;
list_for_each_entry_safe(ind, tmp, &pdr->indack_list, node) {
pds = ind->pds;
mutex_lock(&pdr->status_lock);
pds->state = ind->curr_state;
pdr->status(pds->state, pds->service_path, pdr->priv);
mutex_unlock(&pdr->status_lock);
/* Ack the indication after clients release the PD resources */
pdr_send_indack_msg(pdr, pds, ind->transaction_id);
mutex_lock(&pdr->list_lock);
list_del(&ind->node);
mutex_unlock(&pdr->list_lock);
kfree(ind);
}
}
static void pdr_indication_cb(struct qmi_handle *qmi,
struct sockaddr_qrtr *sq,
struct qmi_txn *txn, const void *data)
{
struct pdr_handle *pdr = container_of(qmi, struct pdr_handle,
notifier_hdl);
const struct servreg_state_updated_ind *ind_msg = data;
struct pdr_list_node *ind;
struct pdr_service *pds;
bool found = false;
if (!ind_msg || !ind_msg->service_path[0] ||
strlen(ind_msg->service_path) > SERVREG_NAME_LENGTH)
return;
mutex_lock(&pdr->list_lock);
list_for_each_entry(pds, &pdr->lookups, node) {
if (strcmp(pds->service_path, ind_msg->service_path))
continue;
found = true;
break;
}
mutex_unlock(&pdr->list_lock);
if (!found)
return;
pr_info("PDR: Indication received from %s, state: 0x%x, trans-id: %d\n",
ind_msg->service_path, ind_msg->curr_state,
ind_msg->transaction_id);
ind = kzalloc(sizeof(*ind), GFP_KERNEL);
if (!ind)
return;
ind->transaction_id = ind_msg->transaction_id;
ind->curr_state = ind_msg->curr_state;
ind->pds = pds;
mutex_lock(&pdr->list_lock);
list_add_tail(&ind->node, &pdr->indack_list);
mutex_unlock(&pdr->list_lock);
queue_work(pdr->indack_wq, &pdr->indack_work);
}
static const struct qmi_msg_handler qmi_indication_handler[] = {
{
.type = QMI_INDICATION,
.msg_id = SERVREG_STATE_UPDATED_IND_ID,
.ei = servreg_state_updated_ind_ei,
.decoded_size = sizeof(struct servreg_state_updated_ind),
.fn = pdr_indication_cb,
},
{}
};
static int pdr_get_domain_list(struct servreg_get_domain_list_req *req,
struct servreg_get_domain_list_resp *resp,
struct pdr_handle *pdr)
{
struct qmi_txn txn;
int ret;
ret = qmi_txn_init(&pdr->locator_hdl, &txn,
servreg_get_domain_list_resp_ei, resp);
if (ret < 0)
return ret;
ret = qmi_send_request(&pdr->locator_hdl,
&pdr->locator_addr,
&txn, SERVREG_GET_DOMAIN_LIST_REQ,
SERVREG_GET_DOMAIN_LIST_REQ_MAX_LEN,
servreg_get_domain_list_req_ei,
req);
if (ret < 0) {
qmi_txn_cancel(&txn);
return ret;
}
ret = qmi_txn_wait(&txn, 5 * HZ);
if (ret < 0) {
pr_err("PDR: %s get domain list txn wait failed: %d\n",
req->service_name, ret);
return ret;
}
if (resp->resp.result != QMI_RESULT_SUCCESS_V01) {
pr_err("PDR: %s get domain list failed: 0x%x\n",
req->service_name, resp->resp.error);
return -EREMOTEIO;
}
return 0;
}
static int pdr_locate_service(struct pdr_handle *pdr, struct pdr_service *pds)
{
struct servreg_get_domain_list_resp *resp;
struct servreg_get_domain_list_req req;
struct servreg_location_entry *entry;
int domains_read = 0;
int ret, i;
resp = kzalloc(sizeof(*resp), GFP_KERNEL);
if (!resp)
return -ENOMEM;
/* Prepare req message */
strscpy(req.service_name, pds->service_name, sizeof(req.service_name));
req.domain_offset_valid = true;
req.domain_offset = 0;
do {
req.domain_offset = domains_read;
ret = pdr_get_domain_list(&req, resp, pdr);
if (ret < 0)
goto out;
for (i = domains_read; i < resp->domain_list_len; i++) {
entry = &resp->domain_list[i];
if (strnlen(entry->name, sizeof(entry->name)) == sizeof(entry->name))
continue;
if (!strcmp(entry->name, pds->service_path)) {
pds->service_data_valid = entry->service_data_valid;
pds->service_data = entry->service_data;
pds->instance = entry->instance;
goto out;
}
}
/* Update ret to indicate that the service is not yet found */
ret = -ENXIO;
/* Always read total_domains from the response msg */
if (resp->domain_list_len > resp->total_domains)
resp->domain_list_len = resp->total_domains;
domains_read += resp->domain_list_len;
} while (domains_read < resp->total_domains);
out:
kfree(resp);
return ret;
}
static void pdr_notify_lookup_failure(struct pdr_handle *pdr,
struct pdr_service *pds,
int err)
{
pr_err("PDR: service lookup for %s failed: %d\n",
pds->service_name, err);
if (err == -ENXIO)
return;
list_del(&pds->node);
pds->state = SERVREG_LOCATOR_ERR;
mutex_lock(&pdr->status_lock);
pdr->status(pds->state, pds->service_path, pdr->priv);
mutex_unlock(&pdr->status_lock);
kfree(pds);
}
static void pdr_locator_work(struct work_struct *work)
{
struct pdr_handle *pdr = container_of(work, struct pdr_handle,
locator_work);
struct pdr_service *pds, *tmp;
int ret = 0;
/* Bail out early if the SERVREG LOCATOR QMI service is not up */
mutex_lock(&pdr->lock);
if (!pdr->locator_init_complete) {
mutex_unlock(&pdr->lock);
pr_debug("PDR: SERVICE LOCATOR service not available\n");
return;
}
mutex_unlock(&pdr->lock);
mutex_lock(&pdr->list_lock);
list_for_each_entry_safe(pds, tmp, &pdr->lookups, node) {
if (!pds->need_locator_lookup)
continue;
ret = pdr_locate_service(pdr, pds);
if (ret < 0) {
pdr_notify_lookup_failure(pdr, pds, ret);
continue;
}
ret = qmi_add_lookup(&pdr->notifier_hdl, pds->service, 1,
pds->instance);
if (ret < 0) {
pdr_notify_lookup_failure(pdr, pds, ret);
continue;
}
pds->need_locator_lookup = false;
}
mutex_unlock(&pdr->list_lock);
}
/**
* pdr_add_lookup() - register a tracking request for a PD
* @pdr: PDR client handle
* @service_name: service name of the tracking request
* @service_path: service path of the tracking request
*
* Registering a pdr lookup allows for tracking the life cycle of the PD.
*
* Return: pdr_service object on success, ERR_PTR on failure. -EALREADY is
* returned if a lookup is already in progress for the given service path.
*/
struct pdr_service *pdr_add_lookup(struct pdr_handle *pdr,
const char *service_name,
const char *service_path)
{
struct pdr_service *pds, *tmp;
int ret;
if (IS_ERR_OR_NULL(pdr))
return ERR_PTR(-EINVAL);
if (!service_name || strlen(service_name) > SERVREG_NAME_LENGTH ||
!service_path || strlen(service_path) > SERVREG_NAME_LENGTH)
return ERR_PTR(-EINVAL);
pds = kzalloc(sizeof(*pds), GFP_KERNEL);
if (!pds)
return ERR_PTR(-ENOMEM);
pds->service = SERVREG_NOTIFIER_SERVICE;
strscpy(pds->service_name, service_name, sizeof(pds->service_name));
strscpy(pds->service_path, service_path, sizeof(pds->service_path));
pds->need_locator_lookup = true;
mutex_lock(&pdr->list_lock);
list_for_each_entry(tmp, &pdr->lookups, node) {
if (strcmp(tmp->service_path, service_path))
continue;
mutex_unlock(&pdr->list_lock);
ret = -EALREADY;
goto err;
}
list_add(&pds->node, &pdr->lookups);
mutex_unlock(&pdr->list_lock);
schedule_work(&pdr->locator_work);
return pds;
err:
kfree(pds);
return ERR_PTR(ret);
}
EXPORT_SYMBOL(pdr_add_lookup);
/**
* pdr_restart_pd() - restart PD
* @pdr: PDR client handle
* @pds: PD service handle
*
* Restarts the PD tracked by the PDR client handle for a given service path.
*
* Return: 0 on success, negative errno on failure.
*/
int pdr_restart_pd(struct pdr_handle *pdr, struct pdr_service *pds)
{
struct servreg_restart_pd_resp resp;
struct servreg_restart_pd_req req = { 0 };
struct sockaddr_qrtr addr;
struct pdr_service *tmp;
struct qmi_txn txn;
int ret;
if (IS_ERR_OR_NULL(pdr) || IS_ERR_OR_NULL(pds))
return -EINVAL;
mutex_lock(&pdr->list_lock);
list_for_each_entry(tmp, &pdr->lookups, node) {
if (tmp != pds)
continue;
if (!pds->service_connected)
break;
/* Prepare req message */
strscpy(req.service_path, pds->service_path, sizeof(req.service_path));
addr = pds->addr;
break;
}
mutex_unlock(&pdr->list_lock);
if (!req.service_path[0])
return -EINVAL;
ret = qmi_txn_init(&pdr->notifier_hdl, &txn,
servreg_restart_pd_resp_ei,
&resp);
if (ret < 0)
return ret;
ret = qmi_send_request(&pdr->notifier_hdl, &addr,
&txn, SERVREG_RESTART_PD_REQ,
SERVREG_RESTART_PD_REQ_MAX_LEN,
servreg_restart_pd_req_ei, &req);
if (ret < 0) {
qmi_txn_cancel(&txn);
return ret;
}
ret = qmi_txn_wait(&txn, 5 * HZ);
if (ret < 0) {
pr_err("PDR: %s PD restart txn wait failed: %d\n",
req.service_path, ret);
return ret;
}
/* Check response if PDR is disabled */
if (resp.resp.result == QMI_RESULT_FAILURE_V01 &&
resp.resp.error == QMI_ERR_DISABLED_V01) {
pr_err("PDR: %s PD restart is disabled: 0x%x\n",
req.service_path, resp.resp.error);
return -EOPNOTSUPP;
}
/* Check the response for other error case*/
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
pr_err("PDR: %s request for PD restart failed: 0x%x\n",
req.service_path, resp.resp.error);
return -EREMOTEIO;
}
return 0;
}
EXPORT_SYMBOL(pdr_restart_pd);
/**
* pdr_handle_alloc() - initialize the PDR client handle
* @status: function to be called on PD state change
* @priv: handle for client's use
*
* Initializes the PDR client handle to allow for tracking/restart of PDs.
*
* Return: pdr_handle object on success, ERR_PTR on failure.
*/
struct pdr_handle *pdr_handle_alloc(void (*status)(int state,
char *service_path,
void *priv), void *priv)
{
struct pdr_handle *pdr;
int ret;
if (!status)
return ERR_PTR(-EINVAL);
pdr = kzalloc(sizeof(*pdr), GFP_KERNEL);
if (!pdr)
return ERR_PTR(-ENOMEM);
pdr->status = status;
pdr->priv = priv;
mutex_init(&pdr->status_lock);
mutex_init(&pdr->list_lock);
mutex_init(&pdr->lock);
INIT_LIST_HEAD(&pdr->lookups);
INIT_LIST_HEAD(&pdr->indack_list);
INIT_WORK(&pdr->locator_work, pdr_locator_work);
INIT_WORK(&pdr->notifier_work, pdr_notifier_work);
INIT_WORK(&pdr->indack_work, pdr_indack_work);
pdr->notifier_wq = create_singlethread_workqueue("pdr_notifier_wq");
if (!pdr->notifier_wq) {
ret = -ENOMEM;
goto free_pdr_handle;
}
pdr->indack_wq = alloc_ordered_workqueue("pdr_indack_wq", WQ_HIGHPRI);
if (!pdr->indack_wq) {
ret = -ENOMEM;
goto destroy_notifier;
}
ret = qmi_handle_init(&pdr->locator_hdl,
SERVREG_GET_DOMAIN_LIST_RESP_MAX_LEN,
&pdr_locator_ops, NULL);
if (ret < 0)
goto destroy_indack;
ret = qmi_add_lookup(&pdr->locator_hdl, SERVREG_LOCATOR_SERVICE, 1, 1);
if (ret < 0)
goto release_qmi_handle;
ret = qmi_handle_init(&pdr->notifier_hdl,
SERVREG_STATE_UPDATED_IND_MAX_LEN,
&pdr_notifier_ops,
qmi_indication_handler);
if (ret < 0)
goto release_qmi_handle;
return pdr;
release_qmi_handle:
qmi_handle_release(&pdr->locator_hdl);
destroy_indack:
destroy_workqueue(pdr->indack_wq);
destroy_notifier:
destroy_workqueue(pdr->notifier_wq);
free_pdr_handle:
kfree(pdr);
return ERR_PTR(ret);
}
EXPORT_SYMBOL(pdr_handle_alloc);
/**
* pdr_handle_release() - release the PDR client handle
* @pdr: PDR client handle
*
* Cleans up pending tracking requests and releases the underlying qmi handles.
*/
void pdr_handle_release(struct pdr_handle *pdr)
{
struct pdr_service *pds, *tmp;
if (IS_ERR_OR_NULL(pdr))
return;
mutex_lock(&pdr->list_lock);
list_for_each_entry_safe(pds, tmp, &pdr->lookups, node) {
list_del(&pds->node);
kfree(pds);
}
mutex_unlock(&pdr->list_lock);
cancel_work_sync(&pdr->locator_work);
cancel_work_sync(&pdr->notifier_work);
cancel_work_sync(&pdr->indack_work);
destroy_workqueue(pdr->notifier_wq);
destroy_workqueue(pdr->indack_wq);
qmi_handle_release(&pdr->locator_hdl);
qmi_handle_release(&pdr->notifier_hdl);
kfree(pdr);
}
EXPORT_SYMBOL(pdr_handle_release);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Qualcomm Protection Domain Restart helpers");