Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return error for current_exe on nonexistent file #69557

Closed
wants to merge 2 commits into from
Closed

Return error for current_exe on nonexistent file #69557

wants to merge 2 commits into from

Conversation

tsurai
Copy link
Contributor

@tsurai tsurai commented Feb 28, 2020

The underlying implementation of current_exe on linux uses the symlink at /proc/self/exe to find its own executable path. Deleting or replacing the original executable results in a PathBuf containing the original path with " (deleted)" appended effectively pointing to a nonexistent file.

This change only tests the existence of the file not whether the user can actually access it.

Fix #69343

The underlying implementation of current_exe uses the symlink at
/proc/self/exe to find its own executable path. Deleting or replacing
the original executable results in a PathBuf containing the original
path with " (deleted)" appended effectively pointing to a nonexistent
file

Signed-off-by: Cristian Kubis <cristian.kubis@tsunix.de>
@rust-highfive
Copy link
Collaborator

r? @shepmaster

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Feb 28, 2020
@rust-highfive
Copy link
Collaborator

The job x86_64-gnu-llvm-7 of your PR failed (pretty log, raw log). Through arcane magic we have determined that the following fragments from the build log may contain information about the problem.

Click to expand the log.
2020-02-28T16:50:21.0097277Z ========================== Starting Command Output ===========================
2020-02-28T16:50:21.0101557Z [command]/bin/bash --noprofile --norc /home/vsts/work/_temp/7f1e1812-d0fd-4f3b-b7e4-61bfa1fc9ab3.sh
2020-02-28T16:50:21.0101993Z 
2020-02-28T16:50:21.0106142Z ##[section]Finishing: Disable git automatic line ending conversion
2020-02-28T16:50:21.0141352Z ##[section]Starting: Checkout rust-lang/rust@refs/pull/69557/merge to s
2020-02-28T16:50:21.0145347Z Task         : Get sources
2020-02-28T16:50:21.0145635Z Description  : Get sources from a repository. Supports Git, TfsVC, and SVN repositories.
2020-02-28T16:50:21.0145912Z Version      : 1.0.0
2020-02-28T16:50:21.0146120Z Author       : Microsoft
---
2020-02-28T16:50:22.0012348Z ##[command]git remote add origin https://github.com/rust-lang/rust
2020-02-28T16:50:22.0019959Z ##[command]git config gc.auto 0
2020-02-28T16:50:22.0026363Z ##[command]git config --get-all http.https://github.com/rust-lang/rust.extraheader
2020-02-28T16:50:22.0032675Z ##[command]git config --get-all http.proxy
2020-02-28T16:50:22.0039772Z ##[command]git -c http.extraheader="AUTHORIZATION: basic ***" fetch --force --tags --prune --progress --no-recurse-submodules --depth=2 origin +refs/heads/*:refs/remotes/origin/* +refs/pull/69557/merge:refs/remotes/pull/69557/merge

I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact @TimNN. (Feature Requests)

Signed-off-by: Cristian Kubis <cristian.kubis@tsunix.de>
@shepmaster
Copy link
Member

Adding extra syscalls is something that feels suspicious; so randomly reassigning (hopefully to someone more knowledgable!)...

r? @KodrAus

@rust-highfive rust-highfive assigned KodrAus and unassigned shepmaster Feb 28, 2020
Ok(path) => {
let path_str = path.to_str().ok_or(io::Error::new(
io::ErrorKind::InvalidData,
"path contains invalid UTF-8 data",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems likely to be incorrect... we shouldn't need to require a path to be UTF-8; that's the entire reason we have Path(Buf) and OsStr(ing).

"path contains invalid UTF-8 data",
))?;
unsafe {
if libc::access(path_str.as_ptr() as *const i8, libc::F_OK) == 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this FFI call require a NUL-terminated string? Rust strings are not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way lstat is the better way to test if the path indicated exists or not; access checks if the current user can also read the path. Though this gets weird if you do create a file with the (deleted) suffix on in; this will detect it as existing, but the link is actually broken (/proc is, probably not surprisingly, magic here). I think stat("/proc/self/exe") may actually be a better solution for checking if the path exists.

I don't know; there's no good fool-proof way to do this; we're just trying to dodge the throwing knives someone is sending our way at this point.

Copy link
Member

@the8472 the8472 Mar 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clarfon outlined a possible approach, but that takes at least 4 syscalls. #69343 (comment)

I'm not sure if that's worth it because under race conditions paths must be considered stale the very moment you get them from a syscall. The only race-free way to do these things is to operate on file descriptors instead of paths.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, although I will say that the number of syscalls for this seems incredibly reasonable for an API that will be called extremely infrequently (likely seconds between invocations).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that once the symlink is broken due to the target being deleted, it is always broken, so it's at least a one-way gate here. But that cannot rely on readlink followed by use of the path; it must be gotten from stat("/proc/self/exe") directly.

@shepmaster
Copy link
Member

shepmaster commented Feb 28, 2020

In general, this change doesn't seem to solve the problem, it only introduces another time-of-check to time-of-use race condition (which already exists). The caller of this function can still get the same error as we only return a PathBuf. If the file is changed after this function returns but before the file is opened, the same problem occurs.

You should be able to see that with a test like

let a = current_exe();
sleep(1 minute); // go mess with the file now in another window
open(a);

@rust-highfive
Copy link
Collaborator

The job x86_64-gnu-llvm-7 of your PR failed (pretty log, raw log). Through arcane magic we have determined that the following fragments from the build log may contain information about the problem.

Click to expand the log.
2020-02-28T17:12:29.6669759Z ========================== Starting Command Output ===========================
2020-02-28T17:12:29.6672492Z [command]/bin/bash --noprofile --norc /home/vsts/work/_temp/229794d1-dbf7-4d94-a529-92c57cbf8406.sh
2020-02-28T17:12:29.6672819Z 
2020-02-28T17:12:29.6676438Z ##[section]Finishing: Disable git automatic line ending conversion
2020-02-28T17:12:29.6694648Z ##[section]Starting: Checkout rust-lang/rust@refs/pull/69557/merge to s
2020-02-28T17:12:29.6698556Z Task         : Get sources
2020-02-28T17:12:29.6698839Z Description  : Get sources from a repository. Supports Git, TfsVC, and SVN repositories.
2020-02-28T17:12:29.6699115Z Version      : 1.0.0
2020-02-28T17:12:29.6699578Z Author       : Microsoft
---
2020-02-28T17:12:30.6785476Z ##[command]git remote add origin https://github.com/rust-lang/rust
2020-02-28T17:12:30.6790354Z ##[command]git config gc.auto 0
2020-02-28T17:12:30.6793507Z ##[command]git config --get-all http.https://github.com/rust-lang/rust.extraheader
2020-02-28T17:12:30.6796348Z ##[command]git config --get-all http.proxy
2020-02-28T17:12:30.6803411Z ##[command]git -c http.extraheader="AUTHORIZATION: basic ***" fetch --force --tags --prune --progress --no-recurse-submodules --depth=2 origin +refs/heads/*:refs/remotes/origin/* +refs/pull/69557/merge:refs/remotes/pull/69557/merge
---
2020-02-28T18:15:35.0202693Z .................................................................................................... 1700/9735
2020-02-28T18:15:39.4469104Z .................................................................................................... 1800/9735
2020-02-28T18:15:50.3849484Z ......................................................................i............................. 1900/9735
2020-02-28T18:15:56.7092636Z .................................................................................................... 2000/9735
2020-02-28T18:16:12.2050089Z ............................................................iiiii................................... 2100/9735
2020-02-28T18:16:23.3601113Z .................................................................................................... 2300/9735
2020-02-28T18:16:25.8447221Z .................................................................................................... 2400/9735
2020-02-28T18:16:29.1692884Z .................................................................................................... 2500/9735
2020-02-28T18:16:49.1432881Z .................................................................................................... 2600/9735
---
2020-02-28T18:19:26.7768430Z .....................i...............i.............................................................. 5000/9735
2020-02-28T18:19:36.4658504Z .................................................................................................... 5100/9735
2020-02-28T18:19:41.4587294Z ................................................................i................................... 5200/9735
2020-02-28T18:19:47.6029528Z .................................................................................................... 5300/9735
2020-02-28T18:19:55.7630665Z .........................................ii.ii........i...i......................................... 5400/9735
2020-02-28T18:20:04.6146232Z .................................................................................................... 5600/9735
2020-02-28T18:20:14.4683436Z .................................................................................................... 5700/9735
2020-02-28T18:20:21.4226845Z ................................i................................................................... 5800/9735
2020-02-28T18:20:27.7130424Z .................................................................................................... 5900/9735
2020-02-28T18:20:27.7130424Z .................................................................................................... 5900/9735
2020-02-28T18:20:38.2601261Z .................................................................................................... 6000/9735
2020-02-28T18:20:47.3703796Z .......................ii...i..ii...........i....................................................... 6100/9735
2020-02-28T18:21:03.5668534Z .................................................................................................... 6300/9735
2020-02-28T18:21:11.2235400Z .................................................................................................... 6400/9735
2020-02-28T18:21:20.6249715Z .......................................................i.ii......................................... 6500/9735
2020-02-28T18:21:32.9854661Z .................................................................................................... 6600/9735
---
2020-02-28T18:23:33.5065296Z .................................................................................................... 7700/9735
2020-02-28T18:23:38.3677183Z .................................................................................................... 7800/9735
2020-02-28T18:23:43.9215773Z .................................................................................................... 7900/9735
2020-02-28T18:23:52.1991665Z .....................i.............................................................................. 8000/9735
2020-02-28T18:24:00.0539713Z ......................................................................iiiiiii.i..................... 8100/9735
2020-02-28T18:24:15.6195718Z ...........i......i................................................................................. 8300/9735
2020-02-28T18:24:21.3321041Z .................................................................................................... 8400/9735
2020-02-28T18:24:34.9860145Z ....................................F............................................................... 8500/9735
2020-02-28T18:24:44.0729243Z .................................................................................................... 8600/9735
---
2020-02-28T18:26:38.7553358Z 
2020-02-28T18:26:38.7553736Z ------------------------------------------
2020-02-28T18:26:38.7554025Z stderr:
2020-02-28T18:26:38.7554413Z ------------------------------------------
2020-02-28T18:26:38.7555310Z thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: NotFound, error: "executable file does not exist anymore" }', /checkout/src/test/ui/process/process-spawn-with-unicode-params.rs:25:19
2020-02-28T18:26:38.7556233Z 
2020-02-28T18:26:38.7556666Z ------------------------------------------
2020-02-28T18:26:38.7556904Z 
2020-02-28T18:26:38.7557061Z 
---
2020-02-28T18:26:38.7559635Z 
2020-02-28T18:26:38.7560012Z ------------------------------------------
2020-02-28T18:26:38.7560293Z stderr:
2020-02-28T18:26:38.7560685Z ------------------------------------------
2020-02-28T18:26:38.7561497Z thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: NotFound, error: "executable file does not exist anymore" }', /checkout/src/test/ui/stdio-is-blocking.rs:25:14
2020-02-28T18:26:38.7562391Z 
2020-02-28T18:26:38.7562772Z ------------------------------------------
2020-02-28T18:26:38.7563023Z 
2020-02-28T18:26:38.7563183Z 
---
2020-02-28T18:26:38.7571604Z thread 'main' panicked at 'Some tests failed', src/tools/compiletest/src/main.rs:348:22
2020-02-28T18:26:38.7572126Z note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
2020-02-28T18:26:38.7581386Z 
2020-02-28T18:26:38.7581498Z 
2020-02-28T18:26:38.7592360Z command did not execute successfully: "/checkout/obj/build/x86_64-unknown-linux-gnu/stage0-tools-bin/compiletest" "--compile-lib-path" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/lib" "--run-lib-path" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/lib/rustlib/x86_64-unknown-linux-gnu/lib" "--rustc-path" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "--src-base" "/checkout/src/test/ui" "--build-base" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui" "--stage-id" "stage2-x86_64-unknown-linux-gnu" "--mode" "ui" "--target" "x86_64-unknown-linux-gnu" "--host" "x86_64-unknown-linux-gnu" "--llvm-filecheck" "/usr/lib/llvm-7/bin/FileCheck" "--host-rustcflags" "-Crpath -O -Cdebuginfo=0 -Zunstable-options  -Lnative=/checkout/obj/build/x86_64-unknown-linux-gnu/native/rust-test-helpers" "--target-rustcflags" "-Crpath -O -Cdebuginfo=0 -Zunstable-options  -Lnative=/checkout/obj/build/x86_64-unknown-linux-gnu/native/rust-test-helpers" "--docck-python" "/usr/bin/python2.7" "--lldb-python" "/usr/bin/python2.7" "--gdb" "/usr/bin/gdb" "--quiet" "--llvm-version" "7.0.0\n" "--system-llvm" "--cc" "" "--cxx" "" "--cflags" "" "--llvm-components" "" "--adb-path" "adb" "--adb-test-dir" "/data/tmp/work" "--android-cross-path" "" "--color" "always"
2020-02-28T18:26:38.7595356Z 
2020-02-28T18:26:38.7595493Z 
2020-02-28T18:26:38.7606487Z failed to run: /checkout/obj/build/bootstrap/debug/bootstrap test
2020-02-28T18:26:38.7606938Z Build completed unsuccessfully in 1:06:59
2020-02-28T18:26:38.7606938Z Build completed unsuccessfully in 1:06:59
2020-02-28T18:26:38.7653456Z == clock drift check ==
2020-02-28T18:26:38.7673672Z   local time: Fri Feb 28 18:26:38 UTC 2020
2020-02-28T18:26:39.3054208Z   network time: Fri, 28 Feb 2020 18:26:39 GMT
2020-02-28T18:26:39.3054666Z == end clock drift check ==
2020-02-28T18:26:39.7400140Z 
2020-02-28T18:26:39.7470974Z ##[error]Bash exited with code '1'.
2020-02-28T18:26:39.7484506Z ##[section]Finishing: Run build
2020-02-28T18:26:39.7528924Z ##[section]Starting: Checkout rust-lang/rust@refs/pull/69557/merge to s
2020-02-28T18:26:39.7533198Z Task         : Get sources
2020-02-28T18:26:39.7533548Z Description  : Get sources from a repository. Supports Git, TfsVC, and SVN repositories.
2020-02-28T18:26:39.7533863Z Version      : 1.0.0
2020-02-28T18:26:39.7534068Z Author       : Microsoft
2020-02-28T18:26:39.7534068Z Author       : Microsoft
2020-02-28T18:26:39.7534404Z Help         : [More Information](https://go.microsoft.com/fwlink/?LinkId=798199)
2020-02-28T18:26:39.7534826Z ==============================================================================
2020-02-28T18:26:40.0508915Z Cleaning any cached credential from repository: rust-lang/rust (GitHub)
2020-02-28T18:26:40.0550379Z ##[section]Finishing: Checkout rust-lang/rust@refs/pull/69557/merge to s
2020-02-28T18:26:40.0630416Z Cleaning up task key
2020-02-28T18:26:40.0631439Z Start cleaning up orphan processes.
2020-02-28T18:26:40.0783826Z Terminate orphan process: pid (4421) (python)
2020-02-28T18:26:40.0990319Z ##[section]Finishing: Finalize Job

I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact @TimNN. (Feature Requests)

@tsurai
Copy link
Contributor Author

tsurai commented Feb 28, 2020

@shepmaster You are right that it won't fix TOCTOU and that wasn't my intention either. My problem with the current behavior is that the function can return /path/to/executable (deleted) which at no point in time is the correct path the the current exe.
Strictly speaking its not a TOCTOU problem as its already wrong during the time of check. The function just returns something thats not true to begin with.

That being said you are right that you would need to perform additional checks to avoid TOCTOU problems later in your code anyway which makes this redundent. Its highly debatable whether being "more semantically correct" is worth an additional syscall.

@shepmaster
Copy link
Member

You are right that it won't fix TOCTOU and that wasn't my intention either

Ah, my mistake. The question still remains about the overall usefulness of this change, but I can’t speak to it.

@nagisa
Copy link
Member

nagisa commented Feb 29, 2020

Probably the right way to do this would be to do a statx instead of read_link and then get the filename from the structure returned by statx.

@nagisa
Copy link
Member

nagisa commented Feb 29, 2020

oh wait that doesn’t return the filename… Maybe canonicalize the /proc/self/exe path instead?

@the8472
Copy link
Member

the8472 commented Feb 29, 2020

This check would introduce a false negative when the current process does not have search permissions for one of the directories in the path, which is not the same as the file not existing. This may be relevant when passing the path to a setuid or sending it to a differently privileged process via IPC.

@JohnCSimon JohnCSimon added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 11, 2020
@joelpalmer
Copy link

Ping from Triage: @tsurai Any updates?

@tsurai
Copy link
Contributor Author

tsurai commented Mar 23, 2020

I've stopped working on this as I'm waiting for the lib subteam to decide wether this is a bug, expected behavior or a wontfix because of the introduction of an additional syscall.

@JohnCSimon JohnCSimon added the S-blocked Status: Marked as blocked ❌ on something else such as an RFC or other implementation work. label Mar 28, 2020
@Dylan-DPC-zz Dylan-DPC-zz added S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). and removed S-blocked Status: Marked as blocked ❌ on something else such as an RFC or other implementation work. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Mar 29, 2020
@crlf0710
Copy link
Member

crlf0710 commented Apr 5, 2020

@rustbot modify labels to +T-libs +I-nominated

@rustbot
Copy link
Collaborator

rustbot commented Apr 5, 2020

Error: Label I-nominated can only be set by Rust team members

Please let @rust-lang/release know if you're having trouble with this bot.

@crlf0710
Copy link
Member

crlf0710 commented Apr 5, 2020

@rustbot modify labels to +T-libs

@rustbot rustbot added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Apr 5, 2020
@Amanieu
Copy link
Member

Amanieu commented Apr 5, 2020

There are many ways in which the string returned by readlink(/proc/self/exe) can be misleading:

  • It could point to a deleted file (" (deleted)" suffix).
  • It could point to a path outside the current chroot namespace.
  • It could even point to something that isn't even a file, e.g. a memfd.

In light of this I feel that we should simply update the documentation of current_exe to say that this function is "best effort" but shouldn't be relied on to return a valid path. It should mainly be used for logging/debugging output.

@rfcbot fcp close

@rfcbot
Copy link

rfcbot commented Apr 5, 2020

Team member @Amanieu has proposed to close this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-close This PR / issue is in PFCP or FCP with a disposition to close it. labels Apr 5, 2020
@SimonSapin
Copy link
Contributor

One use case of current_exe is to pass its return value to Command::new in order to spawn a new process of the same executable (possibly with different arguments or env variables).

However on Linux it might be better to use Command::new("/proc/self/exe") with the symlink name since that seems to work even when the symlink’s target has been deleted.

Perhaps we could add something like Command::for_current_exe() that uses such a symlink on systems that have one, and falls back to Command::new(current_exe()?) on others? It would still be best-effort, but slightly better. And have the docs for current_exe recommend that as well.

@rfcbot rfcbot added the final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. label Apr 15, 2020
@rfcbot
Copy link

rfcbot commented Apr 15, 2020

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot removed the proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. label Apr 15, 2020
@mathstuf
Copy link
Contributor

Why would that need to be something in the stdlib? Sounds like a great use for a crate though.

FWIW, my usual use case is to discover the location of the executable in order to derive paths relative to it for resources or whatnot. Typically used in tests since assuming it is not deleted is fine there or the path manipulation is still valid even with (deleted) "targets". Rarely have I needed it, personally, from "live" code.

@clarfonthey
Copy link
Contributor

Potential other option -- why not just deprecate current_exe because it seems that this kind of detection is way more complicated than it appears to be on the surface, and that people should really be using a separate crate depending on their use case?

@mathstuf
Copy link
Contributor

I think the documentation is on the right track for that. But I do think wording that it is just a path and that the path itself, even if fetched correctly, may not be the real path that was used to launch the process due to various reasons. Maybe just clarifying that "security implications" includes things like "supporting reexec" or other examples.

@Kixunil
Copy link
Contributor

Kixunil commented Apr 23, 2020

@clarfon I don't think Rust should deprecate anything unless:

  • There's a better method to achieve the goal (none yet)
  • Introduces soundness bug (it doens't)

@clarfonthey
Copy link
Contributor

@Kixunil This has been done in the past with os::home_dir, where the behaviour is unexpected but not unsound. Only difference there is that there is a crate for that and one for this doesn't exist yet. I currently don't have the time or bandwidth to maintain one, but if someone else is willing to do that, we could still deprecate it with similar reasoning.

A good question to ask is why people want this. Is it to read the running binary? To provide a name for the current command in help? Those use cases should be covered outside libstd imho

@Kixunil
Copy link
Contributor

Kixunil commented Apr 24, 2020

@clarfon another crate existing satisfies "a better method to achieve the goal". :)

@rfcbot
Copy link

rfcbot commented Apr 25, 2020

The final comment period, with a disposition to close, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Apr 25, 2020
@crlf0710
Copy link
Member

So, according to the fcp disposition above, i'm gonna go ahead and close this.

@crlf0710 crlf0710 closed this Apr 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-close This PR / issue is in PFCP or FCP with a disposition to close it. finished-final-comment-period The final comment period is finished for this PR / Issue. S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

current_exe() returns invalid path on linux when exe has been deleted