دليل المعاملات

مقدمة

عند استخدام مكتبة C/C++ غير محمية، يضمن الرابط توفّر جميع الوظائف الضرورية بعد التحويل البرمجي، وبالتالي لا داعي للقلق بشأن احتمال إخفاق طلب بيانات من واجهة برمجة التطبيقات في وقت التشغيل.

ومع ذلك، عند استخدام مكتبة محمية في وضع الحماية، يتم تنفيذ المكتبة في عملية منفصلة. يتطلب الإخفاق في استدعاء واجهة برمجة التطبيقات التحقق من جميع أنواع المشكلات المتعلقة بتمرير الاستدعاء عبر طبقة استدعاء إجراء عن بُعد (RPC). في بعض الأحيان، قد لا تكون أخطاء طبقة استدعاء إجراء عن بُعد (RPC) مثيرة للاهتمام، على سبيل المثال عند إجراء المعالجة المجمّعة وإعادة تشغيل وضع الحماية.

ومع ذلك، وللأسباب المذكورة أعلاه، فمن المهم تمديد عملية التحقق المنتظم من الأخطاء في القيمة المعروضة لاستدعاء واجهة برمجة التطبيقات في وضع الحماية لتشمل التحقق مما إذا كان قد تم عرض خطأ في طبقة استدعاء إجراء عن بُعد (RPC). هذا هو السبب في أن جميع النماذج الأوّلية لدوال المكتبة تعرض ::sapi::StatusOr<T> بدلاً من T. في حال تعذّر استدعاء دالة المكتبة (على سبيل المثال، بسبب انتهاك وضع الحماية)، ستحتوي القيمة المعروضة على تفاصيل حول الخطأ الذي حدث.

إن التعامل مع أخطاء طبقة استدعاء إجراء عن بُعد (RPC) يعني أن كل استدعاء للمكتبة التي تم وضع الحماية لها يتبعه فحص إضافي لطبقة استدعاء إجراء عن بُعد (RPC) في SAPI. للتعامل مع هذه المواقف الاستثنائية، توفّر SAPI وحدة معاملات SAPI (transaction.h). تحتوي هذه الوحدة على الفئة ::sapi::Transaction وتتأكد من أنّ جميع استدعاءات الدوالّ الموجَّهة إلى "المكتبة المحمية" قد تم إكمالها بدون أي مشاكل على مستوى RPC، أو أنّها تعرض خطأ ذي صلة.

معاملة SAPI

تعمل SAPI على عزل رمز المضيف من المكتبة التي توفّر وضع الحماية ومنح المتصل القدرة على إعادة تشغيل طلب معالجة البيانات الذي يحتوي على مشاكل أو إلغائه. معاملة SAPI لها خطوة أخرى إلى الأمام وتكرر تلقائيًا العمليات غير الناجحة.

يمكن استخدام معاملات 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 (راجع الجدول أعلاه)، فستتم إعادة تشغيل المعاملة. يتم تحديد العدد التلقائي لعمليات إعادة التشغيل بواسطة kDefaultRetryCnt في transaction.h.

في ما يلي أمثلة على الأخطاء المرفوعة التي ستؤدي إلى إعادة التشغيل:

  • حدث انتهاك في وضع الحماية
  • تعطّلت العملية في وضع الحماية
  • عرضت دالة في وضع الحماية رمز خطأ بسبب خطأ في المكتبة

يتّبع إجراء إعادة التشغيل المسار العادي لـ Init() وMain()، وإذا أدّت الاستدعاءات المتكرّرة لطريقة ::sapi::Transaction::Run() إلى ظهور أخطاء، تعرض الطريقة بأكملها رسالة خطأ للمتصل.

معالجة خطأ في وضع الحماية أو استدعاء إجراء عن بُعد (RPC)

تحاول واجهة المكتبة التي يتم إنشاؤها تلقائيًا في وضع الحماية أن تكون أقرب ما يكون إلى النموذج الأولي لوظيفة مكتبة 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.

ستؤدي إعادة التشغيل إلى إلغاء صلاحية أي مرجع لعملية المكتبة الموضوعة في وضع الحماية. وهذا يعني أنّ أدوات وصف الملفات التي تم تمريرها أو الذاكرة المخصّصة لن تكون متوفّرة بعد الآن.