1/*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include <stdlib.h> /* malloc */
17#include <sys/mman.h> /* mmap */
18#include <pthread.h>
19#include <stdio.h> /* FILE */
20
21#include "securec.h"
22#include "pm_ptr_util.h"
23#include "pm_util.h"
24#include "pm_state_c.h"
25#include "ux_page_table_c.h"
26#include "purgeable_mem_builder_c.h"
27#include "pm_log_c.h"
28#include "purgeable_mem_c.h"
29
30#undef LOG_TAG
31#define LOG_TAG "PurgeableMemC"
32
33struct PurgMem {
34    void *dataPtr;
35    size_t dataSizeInput;
36    struct PurgMemBuilder *builder;
37    UxPageTableStruct *uxPageTable;
38    pthread_rwlock_t rwlock;
39    unsigned int buildDataCount;
40};
41
42static inline void LogPurgMemInfo(struct PurgMem *obj)
43{
44    if (obj == NULL) {
45        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: obj is NULL!", __func__);
46        return;
47    }
48    PM_HILOG_INFO_C(LOG_CORE, "purgMemObj(%{public}lx) dataPtr(%{public}lx) dataSizeInput(%{public}zu)"
49        " builderPtr(%{public}lx) uxpt(%{public}lx)",
50        (unsigned long)obj, (unsigned long)(obj->dataPtr), obj->dataSizeInput,
51        (unsigned long)(obj->builder), (unsigned long)(obj->uxPageTable));
52}
53
54static inline size_t RoundUp(size_t val, size_t align)
55{
56    if (val + align < val || val + align < align) {
57        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: Addition overflow!", __func__);
58        return val;
59    }
60    if (align == 0) {
61        return val;
62    }
63    return ((val + align - 1) / align) * align;
64}
65
66static bool IsPurgMemPtrValid(struct PurgMem *purgObj);
67static bool IsPurged(struct PurgMem *purgObj);
68static int TypeCast(void);
69
70static struct PurgMem *PurgMemCreate_(size_t len, struct PurgMemBuilder *builder)
71{
72    /* PurgMemObj allow no builder temporaily */
73    struct PurgMem *pugObj = NULL;
74    pugObj = (struct PurgMem *)malloc(sizeof(struct PurgMem));
75    if (!pugObj) {
76        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: malloc struct PurgMem fail", __func__);
77        return NULL;
78    }
79    size_t size = RoundUp(len, PAGE_SIZE);
80    int type = TypeCast();
81    pugObj->dataPtr = mmap(NULL, size, PROT_READ | PROT_WRITE, type, -1, 0);
82    if (pugObj->dataPtr == MAP_FAILED) {
83        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: mmap dataPtr fail", __func__);
84        pugObj->dataPtr = NULL;
85        goto free_pug_obj;
86    }
87
88    pugObj->uxPageTable = (UxPageTableStruct *)malloc(UxPageTableSize());
89    if (!(pugObj->uxPageTable)) {
90        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: malloc UxPageTableStruct fail", __func__);
91        goto unmap_data;
92    }
93    PMState err = InitUxPageTable(pugObj->uxPageTable, (uint64_t)(pugObj->dataPtr), size); /* dataPtr is aligned */
94    if (err != PM_OK) {
95        PM_HILOG_ERROR_C(LOG_CORE,
96            "%{public}s: InitUxPageTable fail, %{public}s", __func__, GetPMStateName(err));
97        goto free_uxpt;
98    }
99    int lockInitRet = pthread_rwlock_init(&(pugObj->rwlock), NULL);
100    if (lockInitRet != 0) {
101        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: pthread_rwlock_init fail, %{public}d", __func__, lockInitRet);
102        goto deinit_upt;
103    }
104    pugObj->builder = builder;
105    pugObj->dataSizeInput = len;
106    pugObj->buildDataCount = 0;
107
108    PM_HILOG_INFO_C(LOG_CORE, "%{public}s: LogPurgMemInfo:", __func__);
109    LogPurgMemInfo(pugObj);
110    return pugObj;
111
112deinit_upt:
113    DeinitUxPageTable(pugObj->uxPageTable);
114free_uxpt:
115    free(pugObj->uxPageTable);
116    pugObj->uxPageTable = NULL;
117unmap_data:
118    munmap(pugObj->dataPtr, size);
119    pugObj->dataPtr = NULL;
120free_pug_obj:
121    free(pugObj);
122    pugObj = NULL;
123
124    return NULL;
125}
126
127struct PurgMem *PurgMemCreate(size_t len, PurgMemModifyFunc func, void *funcPara)
128{
129    if (len == 0) {
130        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: input len 0", __func__);
131        return NULL;
132    }
133    /* a PurgMemObj must have builder */
134    IF_NULL_LOG_ACTION(func, "%{public}s: input func is NULL", return NULL);
135    struct PurgMem *purgMemObj = PurgMemCreate_(len, NULL);
136    /* create fail */
137    if (!purgMemObj) {
138        return purgMemObj;
139    }
140
141    if (PurgMemAppendModify(purgMemObj, func, funcPara)) {
142        return purgMemObj;
143    }
144
145    /* append func fail meas create builder failed */
146    PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: append mod func fail", __func__);
147    if (!PurgMemDestroy(purgMemObj)) {
148        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: destroy PurgMem fail after append modFunc fail", __func__);
149    }
150    return NULL;
151}
152
153bool PurgMemDestroy(struct PurgMem *purgObj)
154{
155    IF_NULL_LOG_ACTION(purgObj, "input is NULL", return true);
156    PM_HILOG_INFO_C(LOG_CORE, "%{public}s: LogPurgMemInfo:", __func__);
157    LogPurgMemInfo(purgObj);
158
159    PMState err = PM_OK;
160    /* destroy rwlock */
161    int ret = pthread_rwlock_destroy(&(purgObj->rwlock));
162    if (ret != 0) {
163        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: pthread_rwlock_destroy fail, %{public}d", __func__, ret);
164    }
165    /* destroy builder */
166    if (purgObj->builder) {
167        if (!PurgMemBuilderDestroy(purgObj->builder)) {
168            PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: PurgMemBuilderDestroy fail", __func__);
169            err = PMB_DESTORY_FAIL;
170        } else {
171            purgObj->builder = NULL;
172        }
173    }
174    /* unmap purgeable mem region */
175    if (purgObj->dataPtr) {
176        size_t size = RoundUp(purgObj->dataSizeInput, PAGE_SIZE);
177        if (munmap(purgObj->dataPtr, size) != 0) {
178            PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: munmap dataPtr fail", __func__);
179            err = PM_UNMAP_PURG_FAIL;
180        } else {
181            /* double check munmap result: if uxpte is set to no_present */
182            if (UxpteIsEnabled() && !IsPurged(purgObj)) {
183                PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: munmap dataPtr succ, but uxpte present", __func__);
184                err = PM_UXPT_PRESENT_DATA_PURGED;
185            }
186            purgObj->dataPtr = NULL;
187        }
188    }
189    /* unmap uxpt */
190    if (purgObj->uxPageTable) {
191        PMState deinitRet = DeinitUxPageTable(purgObj->uxPageTable);
192        if (deinitRet != PM_OK) {
193            PM_HILOG_ERROR_C(LOG_CORE,
194                "%{public}s: deinit upt fail, %{public}s", __func__, GetPMStateName(deinitRet));
195            err = deinitRet;
196        } else {
197            free(purgObj->uxPageTable);
198            purgObj->uxPageTable = NULL;
199        }
200    }
201
202    if (err == PM_OK) {
203        free(purgObj);
204        purgObj = NULL; /* set input para NULL to avoid UAF */
205        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: succ", __func__);
206        return true;
207    }
208    PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: fail, %{public}s", __func__, GetPMStateName(err));
209    return false;
210}
211
212static bool IsPurgMemPtrValid(struct PurgMem *purgObj)
213{
214    IF_NULL_LOG_ACTION(purgObj, "obj is NULL", return false);
215    IF_NULL_LOG_ACTION(purgObj->dataPtr, "dataPtr is NULL", return false);
216    IF_NULL_LOG_ACTION(purgObj->uxPageTable, "pageTable is NULL", return false);
217    IF_NULL_LOG_ACTION(purgObj->builder, "builder is NULL", return false);
218
219    return true;
220}
221
222static inline bool PurgMemBuildData(struct PurgMem *purgObj)
223{
224    bool succ = false;
225    /* clear content before rebuild */
226    if (memset_s(purgObj->dataPtr, RoundUp(purgObj->dataSizeInput, PAGE_SIZE), 0, purgObj->dataSizeInput) != EOK) {
227        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s, clear content fail", __func__);
228        return succ;
229    }
230    /* @purgObj->builder is not NULL since it is checked by IsPurgMemPtrValid() before */
231    succ = PurgMemBuilderBuildAll(purgObj->builder, purgObj->dataPtr, purgObj->dataSizeInput);
232    if (succ) {
233        purgObj->buildDataCount++;
234    }
235    return succ;
236}
237
238static PMState TryBeginRead(struct PurgMem *purgObj)
239{
240    int rwlockRet = pthread_rwlock_rdlock(&(purgObj->rwlock));
241    if (rwlockRet != 0) {
242        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: rdlock fail. %{public}d", __func__, rwlockRet);
243        return PM_LOCK_READ_FAIL;
244    }
245
246    if (!IsPurged(purgObj)) {
247        PM_HILOG_INFO_C(LOG_CORE,
248            "%{public}s: not purged, return true. MAP_PUG=0x%{public}x", __func__, MAP_PURGEABLE);
249        return PM_DATA_NO_PURGED;
250    }
251
252    rwlockRet = pthread_rwlock_unlock(&(purgObj->rwlock));
253    if (rwlockRet != 0) {
254        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: rd unlock fail. %{public}d", __func__, rwlockRet);
255        return PM_UNLOCK_READ_FAIL;
256    }
257
258    return PM_DATA_PURGED;
259}
260
261static PMState BeginReadBuildData(struct PurgMem *purgObj)
262{
263    bool rebuildRet = false;
264    int rwlockRet = pthread_rwlock_wrlock(&(purgObj->rwlock));
265    if (rwlockRet) {
266        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: wrlock fail. %{public}d", __func__, rwlockRet);
267        return PM_LOCK_WRITE_FAIL;
268    }
269
270    if (IsPurged(purgObj)) {
271        rebuildRet = PurgMemBuildData(purgObj);
272        PM_HILOG_ERROR_C(LOG_CORE,
273            "%{public}s: purged, after built %{public}s", __func__, rebuildRet ? "succ" : "fail");
274    }
275
276    rwlockRet = pthread_rwlock_unlock(&(purgObj->rwlock));
277    if (rwlockRet != 0) {
278        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: wr unlock fail. %{public}d", __func__, rwlockRet);
279        return PM_UNLOCK_WRITE_FAIL;
280    }
281
282    if (!rebuildRet) {
283        return PMB_BUILD_ALL_FAIL;
284    }
285
286    return PMB_BUILD_ALL_SUCC;
287}
288
289bool PurgMemBeginRead(struct PurgMem *purgObj)
290{
291    if (!IsPurgMemPtrValid(purgObj)) {
292        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: para is invalid", __func__);
293        return false;
294    }
295    PM_HILOG_INFO_C(LOG_CORE, "%{public}s: LogPurgMemInfo:", __func__);
296    LogPurgMemInfo(purgObj);
297    bool ret = false;
298    PMState err = PM_OK;
299    UxpteGet(purgObj->uxPageTable, (uint64_t)(purgObj->dataPtr), purgObj->dataSizeInput);
300    while (true) {
301        err = TryBeginRead(purgObj);
302        if (err == PM_DATA_NO_PURGED) {
303            ret = true;
304            break;
305        } else if (err != PM_DATA_PURGED) {
306            break;
307        }
308
309        err = BeginReadBuildData(purgObj);
310        if (err != PMB_BUILD_ALL_SUCC) {
311            ret = false;
312            break;
313        }
314    }
315
316    if (!ret) {
317        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: %{public}s, UxptePut.", __func__, GetPMStateName(err));
318        UxptePut(purgObj->uxPageTable, (uint64_t)(purgObj->dataPtr), purgObj->dataSizeInput);
319    }
320    return ret;
321}
322
323bool PurgMemBeginWrite(struct PurgMem *purgObj)
324{
325    if (!IsPurgMemPtrValid(purgObj)) {
326        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: para is invalid", __func__);
327        return false;
328    }
329    PM_HILOG_INFO_C(LOG_CORE, "%{public}s: LogPurgMemInfo:", __func__);
330    LogPurgMemInfo(purgObj);
331    int rwlockRet = 0;
332    bool rebuildRet = false;
333    PMState err = PM_OK;
334
335    UxpteGet(purgObj->uxPageTable, (uint64_t)(purgObj->dataPtr), purgObj->dataSizeInput);
336
337    rwlockRet = pthread_rwlock_wrlock(&(purgObj->rwlock));
338    if (rwlockRet != 0) {
339        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: wrlock fail. %{public}d", __func__, rwlockRet);
340        err = PM_LOCK_WRITE_FAIL;
341        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: %{public}s, return false, UxptePut.", __func__, GetPMStateName(err));
342        UxptePut(purgObj->uxPageTable, (uint64_t)(purgObj->dataPtr), purgObj->dataSizeInput);
343        return false;
344    }
345
346    if (!IsPurged(purgObj)) {
347        return true;
348    }
349
350    /* data is purged */
351    rebuildRet = PurgMemBuildData(purgObj);
352    PM_HILOG_INFO_C(LOG_CORE, "%{public}s: purged, built %{public}s", __func__, rebuildRet ? "succ" : "fail");
353    if (rebuildRet) {
354        return true;
355    }
356    /* data is purged and rebuild failed. return false */
357    err = PMB_BUILD_ALL_FAIL;
358    rwlockRet = pthread_rwlock_unlock(&(purgObj->rwlock));
359    if (rwlockRet != 0) {
360        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: wr unlock fail. %{public}d", __func__, rwlockRet);
361    }
362
363    PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: %{public}s, return false, UxptePut.", __func__, GetPMStateName(err));
364    UxptePut(purgObj->uxPageTable, (uint64_t)(purgObj->dataPtr), purgObj->dataSizeInput);
365    return false;
366}
367
368static inline void EndAccessPurgMem(struct PurgMem *purgObj)
369{
370    if (!IsPurgMemPtrValid(purgObj)) {
371        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: para is invalid", __func__);
372        return;
373    }
374    int rwlockRet = 0;
375    rwlockRet = pthread_rwlock_unlock(&(purgObj->rwlock));
376    if (rwlockRet != 0) {
377        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: unlock fail. %{public}d", __func__, rwlockRet);
378    }
379    UxptePut(purgObj->uxPageTable, (uint64_t)(purgObj->dataPtr), purgObj->dataSizeInput);
380}
381
382void PurgMemEndRead(struct PurgMem *purgObj)
383{
384    EndAccessPurgMem(purgObj);
385}
386
387void PurgMemEndWrite(struct PurgMem *purgObj)
388{
389    EndAccessPurgMem(purgObj);
390}
391
392void *PurgMemGetContent(struct PurgMem *purgObj)
393{
394    if (!IsPurgMemPtrValid(purgObj)) {
395        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: para is invalid", __func__);
396        return NULL;
397    }
398    return purgObj->dataPtr;
399}
400
401size_t PurgMemGetContentSize(struct PurgMem *purgObj)
402{
403    if (!IsPurgMemPtrValid(purgObj)) {
404        PM_HILOG_ERROR_C(LOG_CORE, "%{public}s: para is invalid", __func__);
405        return 0;
406    }
407    return purgObj->dataSizeInput;
408}
409
410bool PurgMemAppendModify(struct PurgMem *purgObj, PurgMemModifyFunc func, void *funcPara)
411{
412    IF_NULL_LOG_ACTION(func, "input func is NULL", return true);
413    IF_NULL_LOG_ACTION(purgObj, "input purgObj is NULL", return false);
414    /* apply modify */
415    bool succ = func(purgObj->dataPtr, purgObj->dataSizeInput, funcPara);
416    if (!succ) {
417        return false;
418    }
419    struct PurgMemBuilder *builder = PurgMemBuilderCreate(func, funcPara, NULL);
420    IF_NULL_LOG_ACTION(builder, "PurgMemBuilderCreate fail", return false);
421
422    if (purgObj->builder == NULL) { /* PurgMemObj has no builder previous */
423        purgObj->builder = builder;
424        return true;
425    }
426    return PurgMemBuilderAppendBuilder(purgObj->builder, builder);
427}
428
429static bool IsPurged(struct PurgMem *purgObj)
430{
431    /* first access, return true means purged */
432    if (purgObj->buildDataCount == 0) {
433        PM_HILOG_INFO_C(LOG_CORE, "%{public}s, has never built, return true", __func__);
434        return true;
435    }
436    return !UxpteIsPresent(purgObj->uxPageTable, (uint64_t)(purgObj->dataPtr), purgObj->dataSizeInput);
437}
438
439static int TypeCast(void)
440{
441    unsigned int utype = MAP_ANONYMOUS;
442    utype |= (UxpteIsEnabled() ? MAP_PURGEABLE : MAP_PRIVATE);
443    int type = (int) utype;
444    return type;
445}