mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-15 08:30:02 +00:00
rework password login to have a count and reboot/suspend after N retries
The implementation is a bit clumsy because the original premise of the login loop is that it should be infalliable, and because of the isolation of the password mechanism there isn't a great way to pass number of failures back and forth across that firewall (and I don't think there should be). The application calling the password routine tracks failures but the mounting routine has to kick that up to the main loop. However, the PDDB main loop can't get stuck on the mount attempt if you want to suspend, because it has to respond to the suspend request. So the failure message has to get kicked all the way back to the status thread, which is what kicks off the mount, and then handled there.
This commit is contained in:
parent
ec1d1b4444
commit
de41e5ecff
6 changed files with 133 additions and 48 deletions
|
|
@ -119,6 +119,12 @@
|
|||
"zh": "密码错误。 再试一次?",
|
||||
"en-tts": "Incorrect PIN. Try again?"
|
||||
},
|
||||
"pddb.failed_attempts": {
|
||||
"en": "⚠{fails} failed login attempts",
|
||||
"ja": "⚠{fails}回のログイン試行の失敗",
|
||||
"zh": "⚠{fails}次失败的登录尝试",
|
||||
"en-tts": "⚠{fails} failed login attempts"
|
||||
},
|
||||
"pddb.checkpass": {
|
||||
"en": "Press any key, then re-enter your PIN to confirm.",
|
||||
"ja": "任意キーを押して、パスワードを再入力してセットアップを確認してください。",
|
||||
|
|
|
|||
|
|
@ -166,6 +166,8 @@ pub(crate) struct PddbOs {
|
|||
entropy: Rc<RefCell<TrngPool>>,
|
||||
/// connection to the password request manager
|
||||
pw_cid: xous::CID,
|
||||
/// Number of consecutive failed login attempts
|
||||
failed_logins: u64,
|
||||
#[cfg(all(feature="pddbtest", feature="autobasis"))]
|
||||
testnames: HashSet::<String>,
|
||||
}
|
||||
|
|
@ -218,6 +220,7 @@ impl PddbOs {
|
|||
dna_mode: DnaMode::Normal,
|
||||
entropy: trngpool,
|
||||
pw_cid,
|
||||
failed_logins: 0,
|
||||
#[cfg(all(feature="pddbtest", feature="autobasis"))]
|
||||
testnames: HashSet::new(),
|
||||
};
|
||||
|
|
@ -246,6 +249,7 @@ impl PddbOs {
|
|||
dna_mode: DnaMode::Normal,
|
||||
entropy: trngpool,
|
||||
pw_cid,
|
||||
failed_logins: 0,
|
||||
#[cfg(all(feature="pddbtest", feature="autobasis"))]
|
||||
testnames: HashSet::new(),
|
||||
}
|
||||
|
|
@ -660,7 +664,8 @@ impl PddbOs {
|
|||
}
|
||||
_ => {
|
||||
log::error!("Couldn't unwrap our system key: {:?}", e);
|
||||
return PasswordState::Incorrect;
|
||||
self.failed_logins = self.failed_logins.saturating_add(1);
|
||||
return PasswordState::Incorrect(self.failed_logins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -675,7 +680,8 @@ impl PddbOs {
|
|||
}
|
||||
_ => {
|
||||
log::error!("Couldn't unwrap our system key: {:?}", e);
|
||||
return PasswordState::Incorrect;
|
||||
self.failed_logins = self.failed_logins.saturating_add(1);
|
||||
return PasswordState::Incorrect(self.failed_logins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -708,8 +714,10 @@ impl PddbOs {
|
|||
for i in 0..syskey_pt.len() {
|
||||
unsafe{nuke.add(i).write_volatile(0)};
|
||||
}
|
||||
self.failed_logins = 0;
|
||||
PasswordState::Correct
|
||||
} else {
|
||||
self.failed_logins = 0;
|
||||
PasswordState::Correct
|
||||
}
|
||||
}
|
||||
|
|
@ -3098,11 +3106,12 @@ impl PddbOs {
|
|||
self.dbg_dump(Some("migration".to_string()), Some(&export));
|
||||
|
||||
// indicate the migration worked
|
||||
self.failed_logins = 0;
|
||||
PasswordState::Correct
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Couldn't unwrap our system key: {:?}", e);
|
||||
PasswordState::Incorrect
|
||||
PasswordState::Incorrect(self.failed_logins)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,38 +135,55 @@ impl Pddb {
|
|||
self.cb_handle.replace(Some(handle));
|
||||
}
|
||||
}
|
||||
/// This blocks until the PDDB is mounted by the end user. If `None` is specified for the poll_interval_ms,
|
||||
/// A random interval between 1 and 2 seconds is chosen for the poll wait time. Randomizing the waiting time
|
||||
/// helps to level out the task scheduler in the case that many threads are waiting on the PDDB simultaneously.
|
||||
/// This blocks until the PDDB is mounted by the end user. It blocks using the deferred-response pattern,
|
||||
/// so the blocking does not spin-wait; the caller does not consume CPU cycles.
|
||||
///
|
||||
/// This is typically the API call one would use to hold execution of a service until the PDDB is mounted.
|
||||
pub fn is_mounted_blocking(&self) {
|
||||
let ret = send_message(self.conn, Message::new_blocking_scalar(
|
||||
Opcode::IsMounted.to_usize().unwrap(), 0, 0, 0, 0)).expect("couldn't execute IsMounted query");
|
||||
match ret {
|
||||
xous::Result::Scalar1(_code) => {
|
||||
()
|
||||
},
|
||||
_ => panic!("Internal error"),
|
||||
loop {
|
||||
let ret = send_message(self.conn, Message::new_blocking_scalar(
|
||||
Opcode::IsMounted.to_usize().unwrap(), 0, 0, 0, 0)).expect("couldn't execute IsMounted query");
|
||||
match ret {
|
||||
xous::Result::Scalar2(code, _count) => {
|
||||
if code == 0 { // mounted successfully
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ => panic!("Internal error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Attempts to mount the system basis. Returns `true` on success, `false` on failure.
|
||||
/// This call may cause a password request box to pop up, in the case that the boot PIN is not currently cached.
|
||||
pub fn try_mount(&self) -> bool {
|
||||
///
|
||||
/// Returns:
|
||||
/// Ok(true) on successful mount
|
||||
/// Ok(false) on user-directed abort of mount
|
||||
/// Ok(usize) on system-forced abort of mount, with `count` failures
|
||||
pub fn try_mount(&self) -> (bool, usize) {
|
||||
let ret = send_message(self.conn, Message::new_blocking_scalar(
|
||||
Opcode::TryMount.to_usize().unwrap(), 0, 0, 0, 0)).expect("couldn't execute IsMounted query");
|
||||
Opcode::TryMount.to_usize().unwrap(), 0, 0, 0, 0)).expect("couldn't execute TryMounted query");
|
||||
match ret {
|
||||
xous::Result::Scalar1(code) => {
|
||||
if code == 0 {false} else {true}
|
||||
xous::Result::Scalar2(code, count) => {
|
||||
if code == 0 {
|
||||
// mounted successfully
|
||||
(true, count)
|
||||
} else if code == 1 {
|
||||
// user aborted
|
||||
(true, count)
|
||||
} else {
|
||||
// system aborted with `count` retries
|
||||
(false, count)
|
||||
}
|
||||
},
|
||||
_ => panic!("Internal error"),
|
||||
_ => panic!("TryMount unexpected return result"),
|
||||
}
|
||||
}
|
||||
/// Unmounts the PDDB. First attempts to unmount any open secret bases, and then finally unmounts
|
||||
/// the system basis. Returns `true` on success, `false` on failure.
|
||||
pub fn try_unmount(&self) -> bool {
|
||||
let ret = send_message(self.conn, Message::new_blocking_scalar(
|
||||
Opcode::TryUnmount.to_usize().unwrap(), 0, 0, 0, 0)).expect("couldn't execute IsMounted query");
|
||||
Opcode::TryUnmount.to_usize().unwrap(), 0, 0, 0, 0)).expect("couldn't execute TryUnmount query");
|
||||
match ret {
|
||||
xous::Result::Scalar1(code) => {
|
||||
if code == 0 {false} else {true}
|
||||
|
|
|
|||
|
|
@ -406,7 +406,8 @@ pub(crate) struct BasisRequestPassword {
|
|||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) enum PasswordState {
|
||||
Correct,
|
||||
Incorrect,
|
||||
Incorrect(u64),
|
||||
ForcedAbort(u64),
|
||||
Uninit,
|
||||
}
|
||||
|
||||
|
|
@ -619,49 +620,52 @@ fn wrapped_main() -> ! {
|
|||
}),
|
||||
Opcode::IsMounted => xous::msg_blocking_scalar_unpack!(msg, _, _, _, _, {
|
||||
if basis_cache.basis_count() > 0 { // if there's anything in the cache, we're mounted.
|
||||
xous::return_scalar(msg.sender, 1).expect("couldn't return scalar");
|
||||
xous::return_scalar2(msg.sender, 0, 0).expect("couldn't return scalar");
|
||||
} else {
|
||||
mount_notifications.push(msg.sender); // defer response until later
|
||||
}
|
||||
}),
|
||||
Opcode::TryMount => xous::msg_blocking_scalar_unpack!(msg, _, _, _, _, {
|
||||
if basis_cache.basis_count() > 0 {
|
||||
xous::return_scalar(msg.sender, 1).expect("couldn't return scalar");
|
||||
xous::return_scalar2(msg.sender, 0, 0).expect("couldn't return scalar");
|
||||
} else {
|
||||
if !pddb_os.rootkeys_initialized() {
|
||||
// can't mount if we have no root keys
|
||||
log::info!("{}PDDB.SKIPMOUNT,{}", xous::BOOKEND_START, xous::BOOKEND_END);
|
||||
xous::return_scalar(msg.sender, 0).expect("could't return scalar");
|
||||
xous::return_scalar2(msg.sender, 1, 0).expect("could't return scalar");
|
||||
} else {
|
||||
match ensure_password(&modals, &mut pddb_os, pw_cid) {
|
||||
PasswordState::Correct => {
|
||||
if try_mount_or_format(&modals, &mut pddb_os, &mut basis_cache, PasswordState::Correct, time_resetter) {
|
||||
is_mounted.store(true, Ordering::SeqCst);
|
||||
for requester in mount_notifications.drain(..) {
|
||||
xous::return_scalar(requester, 1).expect("couldn't return scalar");
|
||||
xous::return_scalar2(requester, 0, 0).expect("couldn't return scalar");
|
||||
}
|
||||
assert!(mount_notifications.len() == 0, "apparently I don't understand what drain() does");
|
||||
log::info!("{}PDDB.MOUNTED,{}", xous::BOOKEND_START, xous::BOOKEND_END);
|
||||
xous::return_scalar(msg.sender, 1).expect("couldn't return scalar");
|
||||
xous::return_scalar2(msg.sender, 0, 0).expect("couldn't return scalar");
|
||||
} else {
|
||||
xous::return_scalar(msg.sender, 0).expect("couldn't return scalar");
|
||||
xous::return_scalar2(msg.sender, 1, 0).expect("couldn't return scalar");
|
||||
}
|
||||
},
|
||||
PasswordState::Uninit => {
|
||||
if try_mount_or_format(&modals, &mut pddb_os, &mut basis_cache, PasswordState::Uninit, time_resetter) {
|
||||
is_mounted.store(true, Ordering::SeqCst);
|
||||
for requester in mount_notifications.drain(..) {
|
||||
xous::return_scalar(requester, 1).expect("couldn't return scalar");
|
||||
xous::return_scalar2(requester, 0, 0).expect("couldn't return scalar");
|
||||
}
|
||||
assert!(mount_notifications.len() == 0, "apparently I don't understand what drain() does");
|
||||
log::info!("{}PDDB.MOUNTED,{}", xous::BOOKEND_START, xous::BOOKEND_END);
|
||||
xous::return_scalar(msg.sender, 1).expect("couldn't return scalar");
|
||||
xous::return_scalar2(msg.sender, 0, 0).expect("couldn't return scalar");
|
||||
} else {
|
||||
xous::return_scalar(msg.sender, 0).expect("couldn't return scalar");
|
||||
xous::return_scalar2(msg.sender, 1, 0).expect("couldn't return scalar");
|
||||
}
|
||||
},
|
||||
PasswordState::ForcedAbort(failcount) => {
|
||||
xous::return_scalar2(msg.sender, 2, failcount.min(u32::MAX as u64) as usize).expect("couldn't return scalar");
|
||||
}
|
||||
// user aborted procedure
|
||||
_ => xous::return_scalar(msg.sender, 0).expect("couldn't return scalar"),
|
||||
_ => xous::return_scalar2(msg.sender, 1, 0).expect("couldn't return scalar"),
|
||||
}
|
||||
initial_heap = heap_usage();
|
||||
latest_heap = initial_heap;
|
||||
|
|
@ -1577,23 +1581,39 @@ fn ensure_password(modals: &modals::Modals, pddb_os: &mut PddbOs, _pw_cid: xous:
|
|||
PasswordState::Correct => {
|
||||
return PasswordState::Correct
|
||||
}
|
||||
PasswordState::Incorrect => {
|
||||
PasswordState::Incorrect(failcount) => {
|
||||
pddb_os.clear_password(); // clear the bad password entry
|
||||
log::info!("{}PDDB.BADPW,{}", xous::BOOKEND_START, xous::BOOKEND_END);
|
||||
// check if the user wants to re-try or not.
|
||||
modals.add_list_item(t!("pddb.yes", xous::LANG)).expect("couldn't build radio item list");
|
||||
modals.add_list_item(t!("pddb.no", xous::LANG)).expect("couldn't build radio item list");
|
||||
match modals.get_radiobutton(t!("pddb.badpass", xous::LANG)) {
|
||||
Ok(response) => {
|
||||
if response.as_str() == t!("pddb.yes", xous::LANG) {
|
||||
continue;
|
||||
} else if response.as_str() == t!("pddb.no", xous::LANG) {
|
||||
return PasswordState::Incorrect;
|
||||
} else {
|
||||
panic!("Got unexpected return from radiobutton");
|
||||
if failcount % 3 == 0 {
|
||||
// every three failures kick the failure back up the stack
|
||||
return PasswordState::ForcedAbort(failcount);
|
||||
} else {
|
||||
// check if the user wants to re-try or not.
|
||||
modals.add_list_item(t!("pddb.yes", xous::LANG)).expect("couldn't build radio item list");
|
||||
modals.add_list_item(t!("pddb.no", xous::LANG)).expect("couldn't build radio item list");
|
||||
let fail_string = format!(
|
||||
"{}\n{}",
|
||||
t!("pddb.badpass", xous::LANG),
|
||||
t!("pddb.failed_attempts", xous::LANG)
|
||||
.replace("{fails}", &failcount.to_string())
|
||||
);
|
||||
let prompt = if failcount == 0 {
|
||||
t!("pddb.badpass", xous::LANG)
|
||||
} else {
|
||||
&fail_string
|
||||
};
|
||||
match modals.get_radiobutton(prompt) {
|
||||
Ok(response) => {
|
||||
if response.as_str() == t!("pddb.yes", xous::LANG) {
|
||||
continue;
|
||||
} else if response.as_str() == t!("pddb.no", xous::LANG) {
|
||||
return PasswordState::Incorrect(failcount);
|
||||
} else {
|
||||
panic!("Got unexpected return from radiobutton");
|
||||
}
|
||||
}
|
||||
_ => panic!("get_radiobutton failed"),
|
||||
}
|
||||
_ => panic!("get_radiobutton failed"),
|
||||
}
|
||||
}
|
||||
PasswordState::Uninit => {
|
||||
|
|
@ -1611,6 +1631,7 @@ fn ensure_password(modals: &modals::Modals, pddb_os: &mut PddbOs, _pw_cid: xous:
|
|||
}
|
||||
return PasswordState::Uninit;
|
||||
}
|
||||
PasswordState::ForcedAbort(_) => panic!("ForcedAbort is not expecetd from try_login()"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@
|
|||
"zh": "重新生成密钥操作失败,请重新启动并重试。\n原因:",
|
||||
"en-tts": "Rekey operation failed, please reboot and try again.\nReason: "
|
||||
},
|
||||
"login.fail": {
|
||||
"ja": "⚠ログインに {fails} 回失敗した後、システムが中断されました",
|
||||
"en": "⚠System was suspended after {fails} failed login attempts.",
|
||||
"zh": "⚠{fails}次登录尝试失败后系统被挂起",
|
||||
"en-tts": "⚠System was suspended after {fails} failed login attempts."
|
||||
},
|
||||
"socup.ignore": {
|
||||
"ja": "この更新を無視する",
|
||||
"en": "Ignore this update",
|
||||
|
|
@ -247,10 +253,10 @@
|
|||
"en-tts": "Shutdown"
|
||||
},
|
||||
"mainmenu.shutdown_confirm": {
|
||||
"en": "Confirm shutdown:\nDevice will reamin off until a charging cable is plugged in. Proceed?",
|
||||
"ja": "シャットダウンの確認:\n充電ケーブルが差し込まれるまで、デバイスはオフのままになります。続行しますか?",
|
||||
"zh": "确认关机:\n设备将保持关闭状态,直到插入充电线。继续?",
|
||||
"en-tts": "Confirm shutdown:\nDevice will reamin off until a charging cable is plugged in. Proceed?"
|
||||
"en": "Confirm shutdown:\n🔌Device will reamin off until a charging cable is plugged in. Proceed?",
|
||||
"ja": "シャットダウンの確認:\n🔌充電ケーブルが差し込まれるまで、デバイスはオフのままになります。続行しますか?",
|
||||
"zh": "确认关机:\n🔌设备将保持关闭状态,直到插入充电线。继续?",
|
||||
"en-tts": "Confirm shutdown:\n🔌Device will reamin off until a charging cable is plugged in. Proceed?"
|
||||
},
|
||||
"mainmenu.reboot": {
|
||||
"en": "Reboot",
|
||||
|
|
|
|||
|
|
@ -638,7 +638,33 @@ fn wrapped_main() -> ! {
|
|||
move || {
|
||||
let tt = ticktimer_server::Ticktimer::new().unwrap();
|
||||
tt.sleep_ms(2000).unwrap(); // a brief pause, to allow the other startup bits to finish running
|
||||
pddb::Pddb::new().try_mount();
|
||||
loop {
|
||||
let (no_retry_failure, count) = pddb::Pddb::new().try_mount();
|
||||
if no_retry_failure {
|
||||
// this includes both successfully mounted, and user abort of mount attempt
|
||||
break;
|
||||
} else {
|
||||
// this indicates system was guttered due to a retry failure
|
||||
let xns = xous_names::XousNames::new().unwrap();
|
||||
let susres = susres::Susres::new_without_hook(&xns).unwrap();
|
||||
let llio = llio::Llio::new(&xns);
|
||||
if ((llio.adc_vbus().unwrap() as u32) * 503) < 150_000 {
|
||||
// try to force suspend if possible, so that users who are just playing around with
|
||||
// the device don't run the battery down accidentally.
|
||||
susres.initiate_suspend().ok();
|
||||
tt.sleep_ms(1000).unwrap();
|
||||
let modals = modals::Modals::new(&xns).unwrap();
|
||||
modals.show_notification(
|
||||
&t!("login.fail", xous::LANG).replace("{fails}", &count.to_string()),
|
||||
None
|
||||
).ok();
|
||||
} else {
|
||||
// otherwise force a reboot cycle to slow down guessers
|
||||
susres.reboot(true);
|
||||
tt.sleep_ms(5000).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue