It might be useful to know what that "rather huge security holes" is:
When a binary setuid program file is started:
1.the kernel sets the user id for the process copied from the file inode
2.the kernel reads the first block into memory and the id number identifies what is used to load it; and calls the loader,
3.the loader finishes the load and calls the programs start function passing the parameters.
The process is now running under the new UID.
A setuid shell script has a different (and insecure) start up:
1. the kernel sets the user id for the process copied from the file inode
2. the kernel reads the first block into memory and the identification fails (instead of the id number it sees "#!", or some other ascii sequence).
3. If "some other" is the case, then it uses the SHELL environment variable to identify the program to interpret the file.
4. The kernel then reads the first block of the interpreter, and identifies the loader for the interpreter and calls the loader
5. the loader finishes the load and calls the programs start function, passing the parameters.
6. the interpreter uses its parameters to identify the file it is to interpret, opens the file and begins processing under the UID specified on the script (from step 1).
NOTE: the security failure is a race condition between step 1 (the kernel sets the UID from the inode of the file)
and the time the script interpreter opens the file in step 6.
There is nothing that guarantees that the file the kernel opened (step 1) is the SAME file that the interpreter opens in step 6.
Closing that hole is possible - but it makes the binary startup have two different methods to startup - a long one if it is a shell script (where the kernel would have create a file definition and then pass that file definition to the interpreter which has to know it is being started via a script), and a short one where it is just another binary executable... without a file id being passed. And the only determiniation difference is "set the process UID"... and it isn't known at that time whether it is a binary or a script...
So the Linux developers decided the simpler way to solve the problem was to disallow it.
BTW, guess what - it is possible to have a script be specified as the interpreter for another script:
Code:
$ more *.sh
t1.sh
::::::::::::::
#!/usr/bin/bash
. $1
echo "PARAM=" $PARAM
::::::::::::::
t.sh
::::::::::::::
#!/home/<your user name here>/t1.sh
PARAM="xyz"
$ ./t.sh
PARAM= xyz
So determining exactly HOW to close the hole can get rather complicated and error prone.