One major problem is that you're not limiting your UPDATE statement. You're actually updating EVERY ROW in the entire table. Watch out for that whenever you're writing UPDATE and DELETE statements or you will cause major problems and your co-workers won't thank you. I would suggest you write all of these types of statements as SELECT statements for to ensure that you're properly limiting with the WHERE clause and then convert it to a more dangerous statement.
As for your specific question - yes - you need to loop through every record in the cursor and then update the SINGLE corresponding row in the table.
Here's an updated example:
CREATE OR REPLACE PROCEDURE update_data
IS
CURSOR c1
IS
SELECT primary_key_column, /* SELECT THIS SO THAT YOU CAN IDENTIFY THE SINGLE ROW TO UPDATE */
SUBSTR(t.address, 3, 4) AS info /*NAME THIS SO IT CAN BE REFERENCED IN THE LOOP */
FROM table_1 t
WHERE t.age > 21;
BEGIN
FOR r /* NAME THE ROW */ IN c1 LOOP /* ORACLE AUTMOATICALLY OPENS THE CURSOR HERE */
UPDATE table_1 t
SET t.info = r.info
WHERE t.primary_key_column = r.primary_key_column; /* LIMIT THE UPDATE TO THIS ROW ONLY */
END LOOP; /* ORACLE AUTOMATICALLY CLOSES THE CURSOR HERE */
END;
You could use the OPEN, FETCH, CLOSE method you are currently using with a loop, but I personally think the FOR ... IN syntax is more readable, easier to write, and handles opening and closing cursors for you, so I've used it here.
The main thing to notice is that the UPDATE is now limited to one single record, so that every iteration through the loop will update the same record that you're running SUBSTR on - and NOT the entire table.
Alternatively, this could all be done with a single statement:
UPDATE table_1 t
SET t.info = SUBSTR(t.address, 3, 4)
WHERE t.age > 21;
Just make sure you're properly limiting your UPDATE.