مقدمة
عند استخدام مكتبة C/C++ غير محمية، يضمن الرابط توفُّر جميع الدوال اللازمة بعد عملية الترجمة، وبالتالي لا داعي للقلق بشأن احتمال تعذُّر تنفيذ طلب واجهة برمجة التطبيقات في وقت التشغيل.
ومع ذلك، عند استخدام "مكتبة محمية برمل"، يتم تنفيذ المكتبة في عملية منفصلة. عند تعذّر تنفيذ طلب بيانات من واجهة برمجة التطبيقات، يجب التحقّق من جميع أنواع المشاكل المتعلّقة بتمرير الطلب عبر طبقة RPC. في بعض الأحيان، قد لا تكون أخطاء طبقة RPC مهمة، مثلاً عند إجراء معالجة مجمّعة وإعادة تشغيل وضع الحماية.
ومع ذلك، للأسباب المذكورة أعلاه، من المهم توسيع نطاق عملية التحقّق المنتظمة من الأخطاء في القيمة المعروضة من خلال طلب البيانات من واجهة برمجة التطبيقات في الوضع المحمي لتشمل التحقّق مما إذا تم عرض خطأ في طبقة RPC. لهذا السبب، تعرض جميع نماذج دوال المكتبة القيمة ::sapi::StatusOr<T>
بدلاً من T. في حال تعذّر استدعاء دالة المكتبة (مثلاً بسبب انتهاك وضع الحماية)، ستتضمّن قيمة الإرجاع تفاصيل حول الخطأ الذي حدث.
إنّ معالجة أخطاء طبقة RPC تعني أنّ كل طلب يتم إرساله إلى مكتبة تعمل في بيئة معزولة يتبعه إجراء فحص إضافي لطبقة RPC في SAPI. للتعامل مع هذه الحالات الاستثنائية، توفّر واجهة برمجة التطبيقات SAPI وحدة SAPI Transaction
(transaction.h).
يحتوي هذا النموذج على الفئة ::sapi::Transaction
ويتأكّد من إكمال جميع طلبات استدعاء الدوال في "المكتبة الموضوعة في وضع الحماية" بدون أي مشاكل على مستوى استدعاء الإجراء عن بُعد (RPC)، أو يعرض خطأ ذا صلة.
معاملة SAPI
تعزل واجهة SAPI رمز المضيف عن المكتبة المحصورة في بيئة آمنة وتمنح المتصل القدرة على إعادة تشغيل طلب معالجة البيانات الذي يتضمّن مشاكل أو إيقافه. تتجاوز SAPI Transaction ذلك وتكرّر تلقائيًا العمليات التي تعذّر إجراؤها.
يمكن استخدام معاملات SAPI بطريقتَين مختلفتَين: إما من خلال الوراثة المباشرة من ::sapi::Transaction
، أو باستخدام مؤشرات الدوال التي يتم تمريرها إلى ::sapi::BasicTransaction
.
يتم تحديد معاملات SAPI من خلال إلغاء الوظائف الثلاث التالية:
طُرق المعاملات في واجهة برمجة التطبيقات الخاصة بنظام الدفع الذكي (SAPI) | |
---|---|
::sapi::Transaction::Init() |
وهذا يشبه استدعاء طريقة تهيئة لمكتبة C/C++ عادية. يتم استدعاء الطريقة مرة واحدة فقط أثناء كل معاملة مع "المكتبة المحمية"، ما لم تتم إعادة تشغيل المعاملة. في حال إعادة التشغيل، يتم استدعاء الطريقة مرة أخرى، بغض النظر عن عدد عمليات إعادة التشغيل التي حدثت من قبل. |
::sapi::Transaction::Main() |
يتم استدعاء الطريقة لكل طلب إلى ::sapi::Transaction::Run() . |
::sapi::Transaction::Finish() |
وهذا يشبه استدعاء طريقة تنظيف لمكتبة C/C++ عادية. يتم استدعاء الطريقة مرة واحدة فقط أثناء إتلاف عنصر "معاملة SAPI". |
استخدام المكتبة بشكل عادي
في مشروع لا يتضمّن مكتبات في وضع الحماية، يكون النمط المعتاد عند التعامل مع المكتبات على النحو التالي:
LibInit();
while (data = NextDataToProcess()) {
result += LibProcessData(data);
}
LibClose();
يتم تهيئة المكتبة، ثم يتم استخدام الدوال التي تم تصديرها من المكتبة، وأخيرًا يتم استدعاء دالة إنهاء/إغلاق لتنظيف البيئة.
استخدام المكتبة في بيئة معزولة
في مشروع يتضمّن مكتبات معزولة، يتم تحويل الرمز من استخدام المكتبة العادية إلى مقتطف الرمز التالي عند استخدام المعاملات مع عمليات رد الاتصال:
// Overwrite the Init method, passed to BasicTransaction initialization
::absl::Status Init(::sapi::Sandbox* sandbox) {
// Instantiate the SAPI Object
LibraryAPI lib(sandbox);
// Instantiate the Sandboxed Library
SAPI_RETURN_IF_ERROR(lib.LibInit());
return ::absl::OkStatus();
}
// Overwrite the Finish method, passed to BasicTransaction initialization
::absl::Status Finish(::sapi::Sandbox *sandbox) {
// Instantiate the SAPI Object
LibraryAPI lib(sandbox);
// Clean-up sandboxed library instance
SAPI_RETURN_IF_ERROR(lib.LibClose());
return ::absl::OkStatus();
}
// Wrapper function to process data, passed to Run method call
::absl::Status HandleData(::sapi::Sandbox *sandbox, Data data_to_process,
Result *out) {
// Instantiate the SAPI Object
LibraryAPI lib(sandbox);
// Call the sandboxed function LibProcessData
SAPI_ASSIGN_OR_RETURN(*out, lib.LibProcessData(data_to_process));
return ::absl::OkStatus();
}
void Handle() {
// Use SAPI Transactions by passing function pointers to ::sapi::BasicTransaction
::sapi::BasicTransaction transaction(Init, Finish);
while (data = NextDataToProcess()) {
::sandbox2::Result result;
// call the ::sapi::Transaction::Run() method
transaction.Run(HandleData, data, &result);
// ...
}
// ...
}
يحرص صف المعاملات على إعادة تهيئة المكتبة في حال حدوث خطأ أثناء استدعاء handle_data
، وسنتناول المزيد من التفاصيل حول هذا الموضوع في القسم التالي.
عمليات إعادة تشغيل المعاملات
إذا أدّى طلب من واجهة برمجة التطبيقات لإحدى المكتبات المحصورة في بيئة آمنة إلى حدوث خطأ أثناء تنفيذ طرق SAPI
Transaction (راجِع الجدول أعلاه)، ستتم إعادة تشغيل المعاملة. يتم تحديد عدد عمليات إعادة التشغيل التلقائي من خلال kDefaultRetryCnt
في transaction.h.
في ما يلي أمثلة على الأخطاء التي ستؤدي إلى إعادة التشغيل:
- حدث انتهاك لوضع الحماية
- تعطّلت العملية المحمية
- عرضت دالة في بيئة معزولة رمز خطأ بسبب حدوث خطأ في المكتبة
يتبع إجراء إعادة التشغيل مسار Init()
وMain()
العادي، وإذا كانت عمليات الاستدعاء المتكررة للطريقة ::sapi::Transaction::Run()
تعرض أخطاء، فإن الطريقة بأكملها تعرض خطأ للمستدعي.
معالجة الأخطاء في وضع الحماية أو أخطاء RPC
تحاول واجهة Sandboxed Library التي يتم إنشاؤها تلقائيًا أن تكون مطابقة قدر الإمكان لنموذج دالة مكتبة C/C++ الأصلي. ومع ذلك، يجب أن تكون "المكتبة المحصورة في بيئة آمنة" قادرة على الإشارة إلى أي أخطاء في البيئة الآمنة أو أخطاء RPC.
ويتم تحقيق ذلك من خلال استخدام أنواع العرض ::sapi::StatusOr<T>
(أو ::sapi::Status
للدوال التي تعرض void
)، بدلاً من عرض قيمة العرض للدوال المحصورة في بيئة الاختبار مباشرةً.
توفّر واجهة برمجة التطبيقات SAPI أيضًا بعض وحدات الماكرو الملائمة للتحقّق من كائن حالة SAPI والاستجابة له. يتم تحديد وحدات الماكرو هذه في ملف العنوان status_macro.h.
مقتطف الرمز التالي هو جزء من مثال المجموع ويوضّح استخدام حالة SAPI ووحدات الماكرو:
// Instead of void, use ::sapi::Status
::sapi::Status SumTransaction::Main() {
// Instantiate the SAPI Object
SumApi f(sandbox());
// ::sapi::StatusOr<int> sum(int a, int b)
SAPI_ASSIGN_OR_RETURN(int v, f.sum(1000, 337));
// ...
// ::sapi::Status sums(sapi::v::Ptr* params)
SumParams params;
params.mutable_data()->a = 1111;
params.mutable_data()->b = 222;
params.mutable_data()->ret = 0;
SAPI_RETURN_IF_ERROR(f.sums(params.PtrBoth()));
// ...
// Gets symbol address and prints its value
int *ssaddr;
SAPI_RETURN_IF_ERROR(sandbox()->Symbol(
"sumsymbol", reinterpret_cast<void**>(&ssaddr)));
::sapi::v::Int sumsymbol;
sumsymbol.SetRemote(ssaddr);
SAPI_RETURN_IF_ERROR(sandbox()->TransferFromSandboxee(&sumsymbol));
// ...
return ::sapi::OkStatus();
}
عمليات إعادة التشغيل في وضع الحماية
تتعامل العديد من المكتبات المحصورة في بيئة الاختبار المعزولة مع مدخلات المستخدم الحسّاسة. إذا حدث تلف في المكتبة المعزولة في مرحلة ما وتم تخزين البيانات بين عمليات التشغيل، ستكون هذه البيانات الحسّاسة معرّضة للخطر. على سبيل المثال، إذا بدأت نسخة معزولة من مكتبة Imagemagick في إرسال صور من عملية التشغيل السابقة.
ولتجنُّب هذا السيناريو، يجب عدم إعادة استخدام البيئة التجريبية عدة مرات. لإيقاف إعادة استخدام البيئات التجريبية، يمكن أن يبدأ رمز المضيف إعادة تشغيل عملية المكتبة المحصورة في بيئة رملية باستخدام ::sapi::Sandbox::Restart()
أو ::sapi::Transaction::Restart()
عند استخدام "معاملات SAPI".
ستؤدي إعادة التشغيل إلى إبطال أي إشارة إلى عملية المكتبة المحصورة في بيئة آمنة. وهذا يعني أنّ واصفات الملفات التي تم تمريرها أو الذاكرة المخصّصة لن تكون متاحة بعد ذلك.