Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/wh_dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ static int _checkMemOperAgainstAllowList(const whDmaAddrAllowList* allowlist,
return rc;
}

/* If no allowlist is registered, anything goes */
/* If no allowlist is registered, deny all operations (fail-closed) */
if (allowlist == NULL) {
return WH_ERROR_OK;
return WH_ERROR_ACCESS;
}
Comment on lines +102 to 105
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR also changes the DMA allowlist contract from 'NULL allowlist allows all' to 'NULL allowlist denies all' (fail-closed). That’s a significant behavioral/API change beyond the described PRE/POST cleanup adjustments; consider explicitly calling this out in the PR description (and any release notes/changelog if applicable) so downstream users aren’t surprised by the new default-deny behavior.

Copilot uses AI. Check for mistakes.

/* If a read/write operation is requested, check the transformed address
Expand Down
72 changes: 46 additions & 26 deletions src/wh_server_cert.c
Original file line number Diff line number Diff line change
Expand Up @@ -556,9 +556,10 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,

#ifdef WOLFHSM_CFG_DMA
case WH_MESSAGE_CERT_ACTION_ADDTRUSTED_DMA: {
whMessageCert_AddTrustedDmaRequest req = {0};
whMessageCert_SimpleResponse resp = {0};
void* cert_data = NULL;
whMessageCert_AddTrustedDmaRequest req = {0};
whMessageCert_SimpleResponse resp = {0};
void* cert_data = NULL;
int cert_dma_pre_ok = 0;

if (req_size != sizeof(req)) {
/* Request is malformed */
Expand All @@ -574,6 +575,9 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
resp.rc = wh_Server_DmaProcessClientAddress(
server, req.cert_addr, &cert_data, req.cert_len,
WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0});
if (resp.rc == WH_ERROR_OK) {
cert_dma_pre_ok = 1;
}
}
if (resp.rc == WH_ERROR_OK) {
/* Process the add trusted action */
Expand All @@ -586,9 +590,10 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
(void)WH_SERVER_NVM_UNLOCK(server);
} /* WH_SERVER_NVM_LOCK() */
}
if (resp.rc == WH_ERROR_OK) {
/* Post-process client address */
resp.rc = wh_Server_DmaProcessClientAddress(
/* Always call POST for successful PRE, regardless of operation
* result */
if (cert_dma_pre_ok) {
(void)wh_Server_DmaProcessClientAddress(
server, req.cert_addr, &cert_data, req.cert_len,
WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0});
}
Comment on lines +593 to 599
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DMA READ_POST return code is discarded via a void cast. If the POST callback fails after the certificate is added successfully, this handler will still return WH_ERROR_OK, potentially leaving DMA/cache state inconsistent. Consider propagating POST errors when resp.rc is still WH_ERROR_OK (or logging POST failures).

Copilot uses AI. Check for mistakes.
Expand All @@ -600,11 +605,12 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
}; break;

case WH_MESSAGE_CERT_ACTION_READTRUSTED_DMA: {
whMessageCert_ReadTrustedDmaRequest req = {0};
whMessageCert_SimpleResponse resp = {0};
void* cert_data = NULL;
whMessageCert_ReadTrustedDmaRequest req = {0};
whMessageCert_SimpleResponse resp = {0};
void* cert_data = NULL;
uint32_t cert_len;
whNvmMetadata meta;
int cert_dma_pre_ok = 0;

if (req_size != sizeof(req)) {
/* Request is malformed */
Expand All @@ -620,6 +626,9 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
resp.rc = wh_Server_DmaProcessClientAddress(
server, req.cert_addr, &cert_data, req.cert_len,
WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0});
if (resp.rc == WH_ERROR_OK) {
cert_dma_pre_ok = 1;
}
}
if (resp.rc == WH_ERROR_OK) {
/* Check metadata to see if the certificate is non-exportable */
Expand All @@ -641,10 +650,11 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
(void)WH_SERVER_NVM_UNLOCK(server);
} /* WH_SERVER_NVM_LOCK() */
}
if (resp.rc == WH_ERROR_OK) {
/* Post-process client address */
resp.rc = wh_Server_DmaProcessClientAddress(
server, req.cert_addr, &cert_data, cert_len,
/* Always call POST for successful PRE, regardless of operation
* result */
if (cert_dma_pre_ok) {
(void)wh_Server_DmaProcessClientAddress(
server, req.cert_addr, &cert_data, req.cert_len,
WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0});
}
Comment on lines +653 to 659
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DMA WRITE_POST return code is ignored. If the certificate read succeeded but POST fails, the handler will still return success, which may mean the client buffer wasn't correctly finalized (e.g., cache flush). Consider capturing the POST rc and using it when resp.rc is still WH_ERROR_OK (or logging failures if you need to preserve the main rc).

Copilot uses AI. Check for mistakes.

Expand All @@ -655,10 +665,11 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
}; break;

case WH_MESSAGE_CERT_ACTION_VERIFY_DMA: {
whMessageCert_VerifyDmaRequest req = {0};
whMessageCert_VerifyDmaResponse resp = {0};
void* cert_data = NULL;
whKeyId keyId = WH_KEYID_ERASED;
whMessageCert_VerifyDmaRequest req = {0};
whMessageCert_VerifyDmaResponse resp = {0};
void* cert_data = NULL;
whKeyId keyId = WH_KEYID_ERASED;
int cert_dma_pre_ok = 0;

if (req_size != sizeof(req)) {
/* Request is malformed */
Expand All @@ -677,6 +688,9 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
resp.rc = wh_Server_DmaProcessClientAddress(
server, req.cert_addr, &cert_data, req.cert_len,
WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0});
if (resp.rc == WH_ERROR_OK) {
cert_dma_pre_ok = 1;
}
}
if (resp.rc == WH_ERROR_OK) {
resp.rc = WH_SERVER_NVM_LOCK(server);
Expand All @@ -693,9 +707,10 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
(void)WH_SERVER_NVM_UNLOCK(server);
} /* WH_SERVER_NVM_LOCK() */
}
if (resp.rc == WH_ERROR_OK) {
/* Post-process client address */
resp.rc = wh_Server_DmaProcessClientAddress(
/* Always call POST for successful PRE, regardless of operation
* result */
if (cert_dma_pre_ok) {
(void)wh_Server_DmaProcessClientAddress(
server, req.cert_addr, &cert_data, req.cert_len,
WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0});
Comment on lines +713 to 715
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DMA READ_POST return code is discarded. If cert verification succeeds but POST fails, the response will still indicate success even though the DMA cleanup step failed. Consider propagating POST errors when resp.rc is still WH_ERROR_OK (or logging POST failures).

Suggested change
(void)wh_Server_DmaProcessClientAddress(
server, req.cert_addr, &cert_data, req.cert_len,
WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0});
int post_rc = wh_Server_DmaProcessClientAddress(
server, req.cert_addr, &cert_data, req.cert_len,
WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0});
if ((resp.rc == WH_ERROR_OK) && (post_rc != WH_ERROR_OK)) {
resp.rc = post_rc;
}

Copilot uses AI. Check for mistakes.
}
Expand Down Expand Up @@ -766,9 +781,10 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
#if defined(WOLFHSM_CFG_DMA)
case WH_MESSAGE_CERT_ACTION_VERIFY_ACERT_DMA: {
/* Acert verify request uses standard cert verify request struct */
whMessageCert_VerifyDmaRequest req = {0};
whMessageCert_SimpleResponse resp = {0};
void* cert_data = NULL;
whMessageCert_VerifyDmaRequest req = {0};
whMessageCert_SimpleResponse resp = {0};
void* cert_data = NULL;
int cert_dma_pre_ok = 0;

if (req_size != sizeof(req)) {
/* Request is malformed */
Expand All @@ -783,6 +799,9 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
rc = wh_Server_DmaProcessClientAddress(
server, req.cert_addr, &cert_data, req.cert_len,
WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0});
if (rc == WH_ERROR_OK) {
cert_dma_pre_ok = 1;
}
}
if (rc == WH_ERROR_OK) {
/* Process the verify action */
Expand All @@ -805,9 +824,10 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
resp.rc = rc;
}
}
if (rc == WH_ERROR_OK) {
/* Post-process client address */
rc = wh_Server_DmaProcessClientAddress(
/* Always call POST for successful PRE, regardless of operation
* result */
if (cert_dma_pre_ok) {
(void)wh_Server_DmaProcessClientAddress(
server, req.cert_addr, &cert_data, req.cert_len,
WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0});
}
Comment on lines +827 to 833
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DMA READ_POST return code is discarded. If the verify action succeeds but POST fails, this path will still report success (resp.rc remains WH_ERROR_OK), potentially masking DMA/cache maintenance failures. Consider capturing the POST rc and surfacing it when rc/resp.rc is still WH_ERROR_OK (or logging it).

Copilot uses AI. Check for mistakes.
Expand Down
74 changes: 52 additions & 22 deletions src/wh_server_crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -4784,11 +4784,17 @@ static int _HandleSha256Dma(whServerContext* ctx, uint16_t magic, int devId,
res.dmaAddrStatus.badAddr = req.state;
}
else {
/* Save the client devId to be restored later, when the context is
* copied back into client memory. */
clientDevId = sha256->devId;
/* overwrite the devId to that of the server for local crypto */
sha256->devId = devId;
/* Validate buffLen from untrusted context */
if (sha256->buffLen >= WC_SHA256_BLOCK_SIZE) {
ret = WH_ERROR_BADARGS;
}
else {
/* Save the client devId to be restored later, when the context
* is copied back into client memory. */
clientDevId = sha256->devId;
/* overwrite the devId to that of the server for local crypto */
sha256->devId = devId;
}
Comment on lines +4787 to +4797
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

res is not initialized, and the new buffLen validation introduces an error path where no fields of res are written before translating it into the response. This can leak uninitialized stack data to the client. Initialize res (e.g., zero it at declaration) and ensure res.dmaAddrStatus is set to a deterministic value on all non-OK returns (including WH_ERROR_BADARGS for invalid buffLen).

Copilot uses AI. Check for mistakes.
}
}

Expand Down Expand Up @@ -4906,11 +4912,17 @@ static int _HandleSha224Dma(whServerContext* ctx, uint16_t magic, int devId,
res.dmaAddrStatus.badAddr = req.state;
}
else {
/* Save the client devId to be restored later, when the context is
* copied back into client memory. */
clientDevId = sha224->devId;
/* overwrite the devId to that of the server for local crypto */
sha224->devId = devId;
/* Validate buffLen from untrusted context */
if (sha224->buffLen >= WC_SHA224_BLOCK_SIZE) {
ret = WH_ERROR_BADARGS;
}
else {
/* Save the client devId to be restored later, when the context
* is copied back into client memory. */
clientDevId = sha224->devId;
/* overwrite the devId to that of the server for local crypto */
sha224->devId = devId;
}
Comment on lines +4915 to +4925
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

res is not initialized, and the new buffLen validation can return WH_ERROR_BADARGS without ever populating res before wh_MessageCrypto_TranslateSha2DmaResponse(). That risks leaking uninitialized stack contents in the response. Initialize res and set a deterministic dmaAddrStatus value for this failure path.

Copilot uses AI. Check for mistakes.
}
}

Expand Down Expand Up @@ -5028,11 +5040,17 @@ static int _HandleSha384Dma(whServerContext* ctx, uint16_t magic, int devId,
res.dmaAddrStatus.badAddr = req.state;
}
else {
/* Save the client devId to be restored later, when the context is
* copied back into client memory. */
clientDevId = sha384->devId;
/* overwrite the devId to that of the server for local crypto */
sha384->devId = devId;
/* Validate buffLen from untrusted context */
if (sha384->buffLen >= WC_SHA384_BLOCK_SIZE) {
ret = WH_ERROR_BADARGS;
}
else {
/* Save the client devId to be restored later, when the context
* is copied back into client memory. */
clientDevId = sha384->devId;
/* overwrite the devId to that of the server for local crypto */
sha384->devId = devId;
}
Comment on lines +5043 to +5053
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added buffLen check can fail with WH_ERROR_BADARGS before any fields of res are assigned, but res is still translated into the output buffer. Initialize res (e.g., {0}) and ensure dmaAddrStatus is always set on error to avoid returning uninitialized data.

Copilot uses AI. Check for mistakes.
}
}

Expand Down Expand Up @@ -5150,13 +5168,25 @@ static int _HandleSha512Dma(whServerContext* ctx, uint16_t magic, int devId,
res.dmaAddrStatus.badAddr = req.state;
}
else {
/* Save the client devId to be restored later, when the context is
* copied back into client memory. */
clientDevId = sha512->devId;
/* overwrite the devId to that of the server for local crypto */
sha512->devId = devId;
/* retrieve hash Type to handle 512, 512-224, or 512-256 */
hashType = sha512->hashType;
/* Validate buffLen from untrusted context */
if (sha512->buffLen >= WC_SHA512_BLOCK_SIZE) {
ret = WH_ERROR_BADARGS;
}
else {
/* Save the client devId to be restored later, when the context
* is copied back into client memory. */
clientDevId = sha512->devId;
/* overwrite the devId to that of the server for local crypto */
sha512->devId = devId;
/* retrieve hash Type to handle 512, 512-224, or 512-256 */
hashType = sha512->hashType;
/* Validate hashType from untrusted context */
if (hashType != WC_HASH_TYPE_SHA512 &&
hashType != WC_HASH_TYPE_SHA512_224 &&
hashType != WC_HASH_TYPE_SHA512_256) {
ret = WH_ERROR_BADARGS;
}
Comment on lines +5176 to +5188
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In _HandleSha512Dma, sha512->devId is overwritten before validating the untrusted hashType. If hashType is invalid, this temporarily mutates the client context in a way the other SHA DMA handlers avoid. Consider validating hashType (and buffLen) before mutating the context, or explicitly reverting sha512->devId immediately when validation fails to keep error paths consistent and safer.

Copilot uses AI. Check for mistakes.
}
Comment on lines +5171 to +5189
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the new buffLen/hashType validation, ret can become WH_ERROR_BADARGS without writing anything into res, but res is still translated into the response. Initialize res and ensure res.dmaAddrStatus is deterministic on these validation failures to prevent leaking uninitialized stack data.

Copilot uses AI. Check for mistakes.
}
}

Expand Down
20 changes: 2 additions & 18 deletions src/wh_server_dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,7 @@ int whServerDma_CopyFromClient(struct whServerContext_t* server,
return WH_ERROR_BADARGS;
}

/* Check the server address against the allow list */
rc = wh_Dma_CheckMemOperAgainstAllowList(server->dma.dmaAddrAllowList,
WH_DMA_OPER_CLIENT_READ_PRE,
serverPtr, len);
if (rc != WH_ERROR_OK) {
return rc;
}

/* Process the client address pre-read */
/* Process the client address pre-read (includes allow list check) */
rc = wh_Server_DmaProcessClientAddress(
server, clientAddr, &transformedAddr, len, WH_DMA_OPER_CLIENT_READ_PRE,
flags);
Comment on lines +134 to 137
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description’s 'Files Changed' list does not mention src/wh_server_dma.c (and also omits test/wh_test_clientserver.c), but both are modified in this PR. Please update the PR description to accurately reflect all changed files, especially since this file affects DMA access-control enforcement paths.

Copilot uses AI. Check for mistakes.
Expand Down Expand Up @@ -185,15 +177,7 @@ int whServerDma_CopyToClient(struct whServerContext_t* server,
return WH_ERROR_BADARGS;
}

/* Check the server address against the allow list */
rc = wh_Dma_CheckMemOperAgainstAllowList(server->dma.dmaAddrAllowList,
WH_DMA_OPER_CLIENT_WRITE_PRE,
serverPtr, len);
if (rc != WH_ERROR_OK) {
return rc;
}

/* Process the client address pre-write */
/* Process the client address pre-write (includes allow list check) */
rc = wh_Server_DmaProcessClientAddress(server, clientAddr, &transformedAddr,
len, WH_DMA_OPER_CLIENT_WRITE_PRE,
flags);
Expand Down
30 changes: 22 additions & 8 deletions src/wh_server_nvm.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ int wh_Server_HandleNvmRequest(whServerContext* server,
whMessageNvm_SimpleResponse resp = {0};
void* metadata = NULL;
void* data = NULL;
int metadata_dma_pre_ok = 0;
int data_dma_pre_ok = 0;

if (req_size != sizeof(req)) {
/* Request is malformed */
Expand All @@ -363,11 +365,17 @@ int wh_Server_HandleNvmRequest(whServerContext* server,
resp.rc = wh_Server_DmaProcessClientAddress(
server, req.metadata_hostaddr, &metadata, sizeof(whNvmMetadata),
WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0});
if (resp.rc == 0) {
metadata_dma_pre_ok = 1;
}
Comment on lines 365 to +370
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code mixes literal 0 checks with WH_ERROR_OK checks used elsewhere in the same file. Use WH_ERROR_OK consistently (e.g., resp.rc == WH_ERROR_OK) to avoid ambiguity and make the success condition resilient if the error-code scheme ever changes.

Copilot uses AI. Check for mistakes.
}
if (resp.rc == 0) {
resp.rc = wh_Server_DmaProcessClientAddress(
server, req.data_hostaddr, &data, req.data_len,
WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0});
if (resp.rc == 0) {
data_dma_pre_ok = 1;
}
}
if (resp.rc == 0) {
rc = WH_SERVER_NVM_LOCK(server);
Expand All @@ -381,14 +389,15 @@ int wh_Server_HandleNvmRequest(whServerContext* server,
} /* WH_SERVER_NVM_LOCK() */
resp.rc = rc;
}
if (resp.rc == 0) {
/* perform platform-specific host address processing */
resp.rc = wh_Server_DmaProcessClientAddress(
/* Always call POST for successful PREs, regardless of operation
* result */
if (metadata_dma_pre_ok) {
(void)wh_Server_DmaProcessClientAddress(
server, req.metadata_hostaddr, &metadata, sizeof(whNvmMetadata),
WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0});
}
if (resp.rc == 0) {
resp.rc = wh_Server_DmaProcessClientAddress(
if (data_dma_pre_ok) {
(void)wh_Server_DmaProcessClientAddress(
server, req.data_hostaddr, &data, req.data_len,
WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0});
}
Comment on lines +392 to 403
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return code from the DMA *_POST operation is being discarded. If the main operation succeeds but the POST callback fails (e.g., cache flush/unmap error or allowlist rejection), the handler will still return success, which can leave the client with an inconsistent view of the operation. Consider capturing the POST rc and, if the current resp.rc is still WH_ERROR_OK, propagate the POST failure (or at least log it).

Copilot uses AI. Check for mistakes.
Expand All @@ -405,6 +414,7 @@ int wh_Server_HandleNvmRequest(whServerContext* server,
whNvmMetadata meta = {0};
whNvmSize read_len = 0;
void* data = NULL;
int data_dma_pre_ok = 0;

if (req_size != sizeof(req)) {
/* Request is malformed */
Expand Down Expand Up @@ -441,15 +451,19 @@ int wh_Server_HandleNvmRequest(whServerContext* server,
rc = wh_Server_DmaProcessClientAddress(
server, req.data_hostaddr, &data, req.data_len,
WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0});
if (rc == 0) {
data_dma_pre_ok = 1;
}
}
if (rc == 0) {
/* Process the Read action */
rc = wh_Nvm_ReadChecked(server->nvm, req.id, req.offset,
read_len, (uint8_t*)data);
}
if (rc == 0) {
/* perform platform-specific host address processing */
rc = wh_Server_DmaProcessClientAddress(
/* Always call POST for successful PRE, regardless of read
* result */
if (data_dma_pre_ok) {
(void)wh_Server_DmaProcessClientAddress(
server, req.data_hostaddr, &data, req.data_len,
WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0});
}
Comment on lines +463 to 469
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DMA WRITE_POST return code is ignored. This can cause the server to report success even if the POST step fails, which is especially risky for write operations (e.g., cache maintenance not completed). Consider storing the POST rc and returning it when rc is still WH_ERROR_OK (or logging it if you intentionally want to preserve the read rc).

Copilot uses AI. Check for mistakes.
Expand Down
Loading
Loading