aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lessons_uncategorized/Lesson_Password/README.md361
-rw-r--r--Lessons_uncategorized/Lesson_Password/flowchart.pngbin0 -> 243502 bytes
-rw-r--r--Lessons_uncategorized/Lesson_Password/password1.pngbin0 -> 6469 bytes
-rw-r--r--Lessons_uncategorized/Lesson_Password/password2.pngbin0 -> 5992 bytes
-rw-r--r--Lessons_uncategorized/Lesson_Password/password3.pngbin0 -> 5952 bytes
-rw-r--r--Lessons_uncategorized/Lesson_Password/password4.pngbin0 -> 5959 bytes
-rw-r--r--Lessons_uncategorized/Lesson_Password/password5.pngbin0 -> 5975 bytes
-rw-r--r--Lessons_uncategorized/Lesson_Password/password6.pngbin0 -> 5980 bytes
-rw-r--r--README.md1
9 files changed, 362 insertions, 0 deletions
diff --git a/Lessons_uncategorized/Lesson_Password/README.md b/Lessons_uncategorized/Lesson_Password/README.md
new file mode 100644
index 0000000..5abfdba
--- /dev/null
+++ b/Lessons_uncategorized/Lesson_Password/README.md
@@ -0,0 +1,361 @@
+In this lesson we would investigate the `password` VFR element.
+
+For that let's create a driver `PasswordForm` with the following VFR code (`UefiLessonsPkg/PasswordForm/Form.vfr`):
+```
+#include <Uefi/UefiMultiPhase.h>
+#include "Data.h"
+
+formset
+ guid = FORMSET_GUID,
+ title = STRING_TOKEN(FORMSET_TITLE),
+ help = STRING_TOKEN(FORMSET_HELP),
+
+ varstore VARIABLE_STRUCTURE,
+ name = FormData,
+ guid = STORAGE_GUID;
+
+ defaultstore StandardDefault,
+ prompt = STRING_TOKEN(STANDARD_DEFAULT_PROMPT),
+ attribute = 0x0000;
+
+ form
+ formid = 1,
+ title = STRING_TOKEN(FORMID1_TITLE);
+
+ password
+ varid = FormData.Password,
+ prompt = STRING_TOKEN(PASSWORD_PROMPT),
+ help = STRING_TOKEN(PASSWORD_HELP),
+ minsize = PASSWORD_MIN_LEN,
+ maxsize = PASSWORD_MAX_LEN,
+ endpassword;
+ endform;
+endformset;
+```
+
+As you can see we can set minimal and maximum size for the password via its `minsize`/`maxsize` fields.
+
+The values for these defines and the overall `varstore` structure as usually we would place to the header file (`UefiLessonsPkg/PasswordForm/Data.h`):
+```
+#ifndef _DATA_H_
+#define _DATA_H_
+
+#define FORMSET_GUID {0xe54b953d, 0x7ddc, 0x455c, {0x8c, 0x1a, 0x59, 0x92, 0xbb, 0xc7, 0x72, 0xc4}}
+#define DATAPATH_GUID {0xb289cf9f, 0xf911, 0x41fd, {0x9b, 0xad, 0x2c, 0x92, 0x57, 0x68, 0x86, 0x64}}
+#define STORAGE_GUID {0xe7f2d73c, 0x699a, 0x4606, {0x92, 0xb6, 0xa3, 0x5e, 0x49, 0x27, 0xc4, 0xd4}}
+
+#define PASSWORD_MIN_LEN 6
+#define PASSWORD_MAX_LEN 8
+#define PASSWORD_STORAGE_SIZE 9
+
+
+#pragma pack(1)
+typedef struct {
+ CHAR16 Password[PASSWORD_STORAGE_SIZE];
+} VARIABLE_STRUCTURE;
+#pragma pack()
+
+#endif
+```
+
+Here you can see that internally the `password` element is a simple string. Looking ahead we set the size of the array `PASSWORD_STORAGE_SIZE = (PASSWORD_MAX_LEN + 1)`. This way the password string would be stored along with the null-terminator symbol. This way it would be easier to use standard string handling function on the password data.
+
+Now let's compile our application and try to use it.
+
+Unfortunately when we try to access the `password` element we would get the following error:
+
+![password1](password1.png?raw=true "password1")
+
+So let's add `flags = INTERACTIVE` to our element. As you know with that flag our element would call `EFI_HII_CONFIG_ACCESS_PROTOCOL.Callback()` function when we would access the element (open/close its form, try to change value of the element).
+
+In our case the `password` element is the only element on the form, so all the generated callbacks would be the callbacks generated by it. But as a good tone let's also add `key = KEY_PASSWORD` to the VFR code:
+```
+password
+ varid = FormData.Password,
+ prompt = STRING_TOKEN(PASSWORD_PROMPT),
+ help = STRING_TOKEN(PASSWORD_HELP),
+ flags = INTERACTIVE,
+ key = KEY_PASSWORD,
+ minsize = PASSWORD_MIN_LEN,
+ maxsize = PASSWORD_MAX_LEN,
+endpassword;
+```
+
+Don't forget to add `#define KEY_PASSWORD 0x1234` to the `UefiLessonsPkg/PasswordForm/Data.h`.
+
+Before diving into the actual callback code let's make a short stop exploring the IFR code of the element:
+
+# `EFI_IFR_PASSWORD`
+
+The password element produces the `EFI_IFR_PASSWORD` and `EFI_IFR_END` opcodes: `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PasswordForm/PasswordForm/DEBUG/Form.lst`
+```
+ password
+>00000058: 08 91 06 00 07 00 34 12 01 00 00 00 04 06 00 08 00
+ varid = FormData.Password,
+ prompt = STRING_TOKEN(0x0006),
+ help = STRING_TOKEN(0x0007),
+ flags = INTERACTIVE,
+ key = 0x1234,
+ minsize = 6,
+ maxsize = 8,
+ endpassword;
+>00000069: 29 02
+```
+
+Here is a definition for the `EFI_IFR_PASSWORD`:
+```
+EFI_IFR_PASSWORD
+
+Summary:
+Creates a password question
+
+Prototype:
+#define EFI_IFR_PASSWORD_OP 0x08
+
+typedef struct _EFI_IFR_PASSWORD {
+ EFI_IFR_OP_HEADER Header;
+ EFI_IFR_QUESTION_HEADER Question;
+ UINT16 MinSize;
+ UINT16 MaxSize;
+} EFI_IFR_PASSWORD;
+
+Members:
+Header The sequence that defines the type of opcode as well as the length of the opcode being defined.
+ Header.OpCode = EFI_IFR_PASSWORD_OP
+Question The standard question header
+MinSize The minimum number of characters that can be accepted for this opcode
+MaxSize The maximum number of characters that can be accepted for this opcode
+
+Description:
+Creates a password question in the current form.
+```
+
+We've covered `EFI_IFR_OP_HEADER` and `EFI_IFR_QUESTION_HEADER` many times, and `MinSize`/`MaxSize` are pretty trivial. So there is nothing new for us here. But it is good to know that we understand the underlying IFR code now.
+
+# Callback code
+
+Now we need to write an implementation for the `EFI_HII_CONFIG_ACCESS_PROTOCOL.CallBack()` to correctly handle our password element.
+
+This element is kind of special and UEFI specification even defines a dedicated flowchart about how the browser should communicate with the driver having the `password` element:
+
+![flowchart](flowchart.png?raw=true "flowchart")
+
+The specification also has `part two` of the flowchart, but currently it seems like is not supported in the edk2 code. The `part two` is for `password` elements without the `flags = INTERACTIVE`, but as you saw earlier the Form Browser simply outputs an error for such cases.
+
+Before we would write the necessary callback code we need to understand when and how the Form Browser will call `Callback()`.
+
+Here is couple of observations regarding the Form Browser:
+
+- When we open the form `Callback()` function is called 2 times for the password element, one with the `Action=EFI_BROWSER_ACTION_FORM_OPEN` argument and one with the `Action=EFI_BROWSER_ACTION_RETRIEVE` argument, in both cases the passed agument `Value` would be the string token pointing to the empty string "":
+```
+EFI_BROWSER_ACTION_FORM_OPEN (Value="")
+EFI_BROWSER_ACTION_RETRIEVE (Value="")
+```
+This is even true when the password has some value, the Form Browser still woudn't pass it to `Callback()`. This is a security measure.
+
+- When we close the form `Callback()` function is called 1 time, also with an empty string:
+```
+EFI_BROWSER_ACTION_FORM_CLOSE (Value="")
+```
+
+- All the rest calls of the `Callback()` function would happen with the `Action=EFI_BROWSER_ACTION_CHANGING` argument.
+
+
+We don't need to do anything special in the `EFI_BROWSER_ACTION_FORM_OPEN`/`EFI_BROWSER_ACTION_RETRIEVE` callbacks, so all our handling code can be placed inside the `EFI_BROWSER_ACTION_CHANGING` action handling:
+```cpp
+STATIC
+EFI_STATUS
+EFIAPI
+Callback (
+ IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
+ IN EFI_BROWSER_ACTION Action,
+ IN EFI_QUESTION_ID QuestionId,
+ IN UINT8 Type,
+ IN OUT EFI_IFR_TYPE_VALUE *Value,
+ OUT EFI_BROWSER_ACTION_REQUEST *ActionRequest
+ )
+{
+ if ((QuestionId == KEY_PASSWORD) && (Action == EFI_BROWSER_ACTION_CHANGING)) {
+ <...>
+ }
+
+ return EFI_UNSUPPORTED;
+}
+```
+
+Now to the actual logic. According to the UEFI specification password flowchart when you access the password element (i.e. try to change it) first the browser asks if the password is already set. For that the form browser calls the `Callback()` function with a `Value` string token pointing to an empty string. In the edk2 code the Form Browser uses `Action=EFI_BROWSER_ACTION_CHANGING` for that action.
+
+Let's start with a case when the password is not yet set. Then the `Callback()` code should return `EFI_SUCCESS` for the above call.
+
+After that the Form Browser prompts user to type new password:
+
+![password2](password2.png?raw=true "password2")
+
+If the input is valid the Form Browser prompts again for password confirmation:
+
+![password3](password3.png?raw=true "password3")
+
+If the user input is incorrect (user has typed password with a length less than password `minsize`, or user has typed the second time different password) the Form Broswer calls the `Callback()` function with the `Value` equal to 0. Keep in mind that since in this case `Value` is a string token, you shouldn't even try to get an actual string from that with the `HiiGetString` call.
+
+Here is a flowchart describing possible form browser calls:
+```
+ EFI_BROWSER_ACTION_CHANGING (Value="")
+[Callback returns EFI_SUCCESS to say that password is not set yet]
+ |
+ "Please type in your new password"
+ ----------------------------------------------------------------------------
+ | |
+ "Please confirm your new password" "Please enter enough characters"
+ | EFI_BROWSER_ACTION_CHANGING (Token=ZERO)
+ |
+ ----------------------------------------------------------------------------------------------------------------------------------
+ | | |
+ EFI_BROWSER_ACTION_CHANGING (Value="<USER INPUT>") "Please enter enough characters" "Passwords are not the same"
+ [Callback sets password and returns EFI_SUCCESS] EFI_BROWSER_ACTION_CHANGING (Token=ZERO) EFI_BROWSER_ACTION_CHANGING (Token=ZERO)
+
+```
+
+Here is a minimal code to support that logic:
+```cpp
+EFI_STATUS HandlePasswordInput(EFI_STRING Password)
+{
+ if (Password[0] == 0) {
+ // Form Browser checks if password exists
+ if (StrLen(FormStorage.Password) != 0) {
+ //TODO
+ }
+ return EFI_SUCCESS;
+ }
+
+ // Form Browser sends 'new passwd' to set
+
+ if (FormStorage.Password[0] == 0) {
+ StrnCpyS(FormStorage.Password, PASSWORD_STORAGE_SIZE, Password, StrLen(Password));
+ return EFI_SUCCESS;
+ }
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+Callback (
+ IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
+ IN EFI_BROWSER_ACTION Action,
+ IN EFI_QUESTION_ID QuestionId,
+ IN UINT8 Type,
+ IN OUT EFI_IFR_TYPE_VALUE *Value,
+ OUT EFI_BROWSER_ACTION_REQUEST *ActionRequest
+ )
+{
+ EFI_STATUS Status;
+ if ((QuestionId == KEY_PASSWORD) && (Action == EFI_BROWSER_ACTION_CHANGING)) {
+ if (Value->string == 0) {
+ return EFI_UNSUPPORTED;
+ }
+
+ EFI_STRING Password = HiiGetString(mHiiHandle, Value->string, "en-US");
+ Status = HandlePasswordInput(Password);
+ FreePool(Password);
+ return Status;
+ }
+
+ return EFI_UNSUPPORTED;
+}
+```
+
+Now we can successfully set initial value for the password from the browser. So it it time to investigate the Form Browser `Callback()` calls for the case when password is already set.
+
+As before, when we try to access the password element, first the browser would ask if the password is already set. But this time we should return an error. We would return `EFI_ALREADY_STARTED` (other errors are possible, but let's stick with that).
+
+After that the Form Browser will ask user to input the current password.
+
+![password4](password4.png?raw=true "password4")
+
+If it has enough characters the browser will pass the input to the `Callback()`. The `Callback()` code should check if the input is equal to the old password. If everything is ok, the code should return `EFI_SUCCESS`.
+
+After that the Form Browser will ask user to type new password:
+
+![password5](password5.png?raw=true "password5")
+
+And if everything correct to confirm it:
+
+![password6](password6.png?raw=true "password6")
+
+The logic is the same as earlier. If the user input is incorrect (user has typed password with a length less than password `minsize`, or user typed the second time different password) the Form Broswer calls the `Callback()` function with the `Value` equal to 0.
+
+Once again here is a flowchart for better understanding:
+```
+ EFI_BROWSER_ACTION_CHANGING (Value="")
+[Callback should return EFI_ALREADY_STARTED to say the password is set]
+ |
+ "Please type in your password"
+ |
+ ----------------------------------------------------------------------------
+ | |
+ EFI_BROWSER_ACTION_CHANGING (Value="<USER INPUT>") "Please enter enough characters"
+[Callback checks if <USER INPUT> equal to current password]
+[and returns EFI_SUCCESS on match, EFI_NOT_READY on fail]
+ |
+ ---------------------------------------------------------------------------
+ | |
+ "Please type in your new password" "Incorrect password"
+ |
+ ----------------------------------------------------------------------------
+ | |
+ "Please confirm your new password" "Please enter enough characters"
+ | EFI_BROWSER_ACTION_CHANGING (Token=ZERO)
+ |
+ ----------------------------------------------------------------------------------------------------------------------------------
+ | | |
+ EFI_BROWSER_ACTION_CHANGING (Value="<USER INPUT>") "Please enter enough characters" "Passwords are not the same"
+ [Callback sets password and returns EFI_SUCCESS] EFI_BROWSER_ACTION_CHANGING (Token=ZERO) EFI_BROWSER_ACTION_CHANGING (Token=ZERO)
+```
+
+Since now the Form Browser can send non-empty string not only to set the new password, but also to verify the old one, we need to understand our place in the flowchart for the correct input handling. For that let's create a global variable `BOOLEAN OldPasswordVerified`:
+- initially we would set this variable to `FALSE`,
+- every time the user resets the Form Browser logic typing incorrect data, we would set it to `FALSE`,
+- when the old password is verified we would set it to `TRUE`,
+- when the password is updated correctly, we would set it back to `FALSE` again.
+
+With everything in mind let's update our `HandlePasswordInput()` code:
+```cpp
+BOOLEAN OldPasswordVerified = FALSE;
+
+EFI_STATUS HandlePasswordInput(EFI_STRING Password)
+{
+ if (Password[0] == 0) {
+ // Form Browser checks if password exists
+ if (FormStorage.Password[0] != 0) {
+ return EFI_ALREADY_STARTED;
+ } else {
+ return EFI_SUCCESS;
+ }
+ } else {
+ // Form Browser sends password value
+ // It can be old password to check or initial/updated password to set
+
+ if (FormStorage.Password[0] == 0) {
+ // Set initial password
+ StrnCpyS(FormStorage.Password, PASSWORD_STORAGE_SIZE, Password, StrLen(Password));
+ return EFI_SUCCESS;
+ }
+
+ if (!OldPasswordVerified) {
+ // Check old password
+ if (StrCmp(Password, FormStorage.Password))
+ return EFI_NOT_READY;
+
+ OldPasswordVerified = TRUE;
+ return EFI_SUCCESS;
+ }
+
+ // Update password
+ StrnCpyS(FormStorage.Password, PASSWORD_STORAGE_SIZE, Password, StrLen(Password));
+ OldPasswordVerified = FALSE;
+ return EFI_SUCCESS;
+ }
+}
+```
+
+With the following code you can set initial password and also have a possibility update it via the Form Browser.
+
diff --git a/Lessons_uncategorized/Lesson_Password/flowchart.png b/Lessons_uncategorized/Lesson_Password/flowchart.png
new file mode 100644
index 0000000..7bee7e0
--- /dev/null
+++ b/Lessons_uncategorized/Lesson_Password/flowchart.png
Binary files differ
diff --git a/Lessons_uncategorized/Lesson_Password/password1.png b/Lessons_uncategorized/Lesson_Password/password1.png
new file mode 100644
index 0000000..63aef82
--- /dev/null
+++ b/Lessons_uncategorized/Lesson_Password/password1.png
Binary files differ
diff --git a/Lessons_uncategorized/Lesson_Password/password2.png b/Lessons_uncategorized/Lesson_Password/password2.png
new file mode 100644
index 0000000..79462d8
--- /dev/null
+++ b/Lessons_uncategorized/Lesson_Password/password2.png
Binary files differ
diff --git a/Lessons_uncategorized/Lesson_Password/password3.png b/Lessons_uncategorized/Lesson_Password/password3.png
new file mode 100644
index 0000000..d951fe1
--- /dev/null
+++ b/Lessons_uncategorized/Lesson_Password/password3.png
Binary files differ
diff --git a/Lessons_uncategorized/Lesson_Password/password4.png b/Lessons_uncategorized/Lesson_Password/password4.png
new file mode 100644
index 0000000..43dfeea
--- /dev/null
+++ b/Lessons_uncategorized/Lesson_Password/password4.png
Binary files differ
diff --git a/Lessons_uncategorized/Lesson_Password/password5.png b/Lessons_uncategorized/Lesson_Password/password5.png
new file mode 100644
index 0000000..136eeb5
--- /dev/null
+++ b/Lessons_uncategorized/Lesson_Password/password5.png
Binary files differ
diff --git a/Lessons_uncategorized/Lesson_Password/password6.png b/Lessons_uncategorized/Lesson_Password/password6.png
new file mode 100644
index 0000000..17c59b0
--- /dev/null
+++ b/Lessons_uncategorized/Lesson_Password/password6.png
Binary files differ
diff --git a/README.md b/README.md
index 69e9d1e..ccd2912 100644
--- a/README.md
+++ b/README.md
@@ -93,6 +93,7 @@ _____
- [Lesson XX](Lessons_uncategorized/Lesson_Varstore_4): Create a driver with `Buffer Storage` - Part 4: Make `ExtractConfig()` function compatible with UEFI specification
- [Lesson XX](Lessons_uncategorized/Lesson_Varstore_5): Investigate when and how the Form Browser calls `EFI_HII_CONFIG_ROUTING_PROTOCOL.Callback()` function
- [Lesson XX](Lessons_uncategorized/Lesson_Varstore_6): Popup windows in edk2. Creating popups with the `EFI_HII_POPUP_PROTOCOL.CreatePopup()` and `CreatePopUp` functions
+- [Lesson XX](Lessons_uncategorized/Lesson_Password): The `password` VFR input element
_____
- [Lesson XX](Lessons_uncategorized/Lesson_Hidden_BIOS_settings): Changing hidden BIOS settings. Using `IFRExtractor` utility to extract UEFI IFR data into human-readable text
_____